30
4
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!
Intrepreting Prindeal
Preprocessing
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.
Variables
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
and Spiny_lumpsuck3r
are different variables.
Execution
Prindeal is an imperative programming language. When a Prindeal program is run its statements are executed from top to bottom in order and then the program ends.
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.)
Non-alias Statements
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
p
and 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
would output
_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
i
and 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
would output
alpaca = 1 alpaca = 2
Note how
alpaca
was incremented from 0 to 1 even though it had never been accessed before.decrement has command name
d
and 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
would output
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]
Where each [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 p
i
d
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
frog
twice. 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 variablefrog
is thus incremented by 2. Theincrement_frog_twice
command 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
would be
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_twice
will be incremented by 2 because1
is 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_twice
on 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
The
set_to_zero
command 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 oryx
is run,d 1
successfully decrementsoryx
from from 3 to 2, thenset_to_zero 1
is called, which is the same as callingset_to_zero oryx
again. So the process repeats untild 1
is a failure, stopping the recursion and increment the_dummy_
variable so a success is produced.
Challenge
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
p
,i
,d
, anda
will 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.
Test Program
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
Scoring
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?
Oh man, I think for once I'll leave the Pyth alone, and pull out a Lisp! One question - functions and variables live in entirely different namespaces, right? So I can increment
p
, and thenp p
, which would print 1, right? – orlp – 2015-08-12T02:17:18.133@orlp Correct. (There are some notes about that in there.) – Calvin's Hobbies – 2015-08-12T02:22:15.060
2I can't be the only one who thinks PRNDL when I see the language name. – Downgoat – 2015-08-12T04:00:07.283
Is there a maximum number of arguments that will be passed to an aliased command? – Zach Gates – 2015-08-12T04:52:54.887
@ZachGates Nope – Calvin's Hobbies – 2015-08-12T04:53:37.890
So, potentially, you could pass a few thousand arguments? Fun. :P @Calvin'sHobbies – Zach Gates – 2015-08-12T04:54:15.510
@ZachGates Yes, though I won't be testing that. (It's fine if your interpreter is limited to 2^32 arguments or whatever :P) – Calvin's Hobbies – 2015-08-12T04:56:09.333
I started to write this in C++. Just the includes needed for map, vector, iostream, string, sstream, utility (and a couple others) exceeded the size of the shortest entry... – Jerry Jeremiah – 2015-08-20T11:29:13.843
Have you considered making it so the parameters that are passed to an aliased command could be command names or variable names? Then the numbers inside the alias command could be used instead of commands or arguments. It kind of gives you function pointers. – Jerry Jeremiah – 2015-08-20T23:21:55.237
I've just released a Turing tarpit called Addict that's based on PRINDEAL. It's much the same, except instead of
– ETHproductions – 2016-09-27T16:16:41.470p
, there's ac
command which outputs as ac
harcode; there's also at
command fort
aking a charcode from input.