37
\$\begingroup\$

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:

  1. Anything after a # sign to the end of the line it's on, plus the # itself. (These are comments.)
  2. Trailing whitespace on any line.
  3. 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:

  1. 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.)

  2. 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.

  3. 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 variable frog is thus incremented by 2. The increment_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 because 1 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 decrements oryx from from 3 to 2, then set_to_zero 1 is called, which is the same as calling set_to_zero oryx again. So the process repeats until d 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, and a 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?

\$\endgroup\$
10
  • \$\begingroup\$ 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 then p p, which would print 1, right? \$\endgroup\$
    – orlp
    Aug 12, 2015 at 2:17
  • \$\begingroup\$ @orlp Correct. (There are some notes about that in there.) \$\endgroup\$ Aug 12, 2015 at 2:22
  • 2
    \$\begingroup\$ I can't be the only one who thinks PRNDL when I see the language name. \$\endgroup\$
    – Downgoat
    Aug 12, 2015 at 4:00
  • \$\begingroup\$ Is there a maximum number of arguments that will be passed to an aliased command? \$\endgroup\$
    – Zach Gates
    Aug 12, 2015 at 4:52
  • \$\begingroup\$ @ZachGates Nope \$\endgroup\$ Aug 12, 2015 at 4:53

13 Answers 13

9
\$\begingroup\$

Pyth, 162 136 bytes

JfTmchcd\#).zKHW<ZlJI!e=T@J~+Z1=@Tk)=k0 .x=J+]h=Nm.xL@Tskd@K=NhT+]+tN0>J~Z0,=Y.x@H=eT0?qN\pps[Td\=dYb)?xGN?qN\iXHThY?YXTH_1=k1XKT:JZ=+Z3

Demonstration.

Golfed out 26 characters by inlining variables and changing from I and E based control flow to ? and .x based control flow.

For the first time ever, I ran out of variables in Pyth. Every single variable in Pyth (bdkGHNTY and JK) was in use, and I wanted to use b as a newline. Fortunaely, I was able to use N to mean two completely different things in different parts of the program, and so it still works.

Ungolfed (run with -m):

JfTmchcd\#).z
KH
W<ZlJ
  I!e=T@J~+Z1
    =@Tk)
  =k0
     .x
      =J+]h=Nm.xL@Tskd@K=NhT+]+tN0>J~Z0
      ,
        =Y.x@H=eT0
        ?qN\p
          ps[Td\=dYb)
          ?xGN
            ?qN\i
              XHThY
              ?Y
                XTH_1
                =k1
            XKT:JZ=+Z3
\$\endgroup\$
2
  • 3
    \$\begingroup\$ I love how I still can't tell what it does even with it ungolfed... \$\endgroup\$ Aug 25, 2015 at 21:31
  • \$\begingroup\$ Well, that concludes Pyth is Non-Turing-Complete... \$\endgroup\$ Oct 20, 2016 at 13:00
9
\$\begingroup\$

Python 2, 600 584 397 373 bytes

This is my own golfed reference solution. Anyone is welcome to improve it or follow its logic in their own answer as long as attribution is given.

The neat part about it is that no recursion is done, so it will never have problems with Python's recursion limit. For example, Sp's Countup Prindeal program can run indefinitely.

p=filter(len,[l.split('#')[0].split()for l in input().split('\n')]);m={};v={};i=0
while i<len(p):
 s=p[i]
 if'('in`s`:s=s[f]
 n,f=s[0],0
 if n in m:a,b,c=([s[int(g)]if g.isdigit()else g for g in t]for t in m[n]);p=[a,(b,c)]+p[i+1:];i=0;continue
 s=s[1]
 q=v.get(s,0)
 if'd'>n:m[s]=p[i+1:i+4];i+=3
 elif'i'<n:print s,'=',q
 elif'd'<n:v[s]=q+1
 elif q:v[s]-=1
 else:f=1
 i+=1

It's a program that takes in the quoted program string with newlines escaped, e.g.
'p _MyVariable_321\np screaming_hairy_armadillo'.

I took various golfing cues from Sp's and Pietu's answers. Thanks guys :)

\$\endgroup\$
0
6
\$\begingroup\$

Python 3, 345 336 335 328 bytes

a=0
A={}
V={}
def f(l):
 if l[0]in"d p i":c,u=l;U=V[u]=V.get(u,0)+"pi".find(c);S=U<0;V[u]+=S;c<"p"or print(u,"=",U)
 else:d=lambda q:[w.isdigit()and l[int(w)]or w for w in A[l[0]][q]];S=f(d(1+f(d(0))))
 return S
for z in open("P"):
 l=z.split("#")[0].split()
 if"a "==z[:2]:a,s,*x=3,l[1]
 elif l*a:x+=l,;a-=1;A[s]=x
 elif l:f(l)

(-6 bytes thanks to @orlp)

Still golfing. Assumes the program is stored in a file named P.

Putting the calls to f inside the lambda d would save a few bytes, but it would make the last test case hit max recursion depth.

Some Prindeal programs

The useless subtraction program

Here is a useless subtraction program. It's useless because, even though it subtracts properly, it doesn't return success/failure accordingly.

The output should be:

a = 15
b = 6
__________ = 0
a = 9
b = 6

Countup

a helper
 p 1
 countup 1
 i success
 
a countup
 i 1
 helper 1
 d failure
 
countup n

Counts upwards and prints n forever. Could possibly work as a test for interpreter speed (beware the long tracebacks on keyboard interrupt).

\$\endgroup\$
2
  • 2
    \$\begingroup\$ Everyone on this question has missed this golf, I don't understand why. l[:(l+"#").find("#")] and all its variations can be replaced by a simple l.split('#')[0]. \$\endgroup\$
    – orlp
    Aug 12, 2015 at 18:37
  • \$\begingroup\$ @orlp Was so focused on find that I forgot you could just split even if #s weren't there. Thanks :) \$\endgroup\$
    – Sp3000
    Aug 13, 2015 at 0:38
6
\$\begingroup\$

JavaScript (ES6), 273 258

Edit Fixed bugs and added a real test suite.

Not counting leading spaces and newlines.

Surely can be golfed a little more.

Too tired to write an explanation now, I think it's a good example of using closures to keep alive temporary values (parameters).

Test running the snippet on any EcmaScript 6 compliant browser (notably not Chrome not MSIE. I tested on Firefox, Safari 9 could go)

F=p=>(
  p=p.match(/^[^#\n]+/gm).filter(r=>r.trim(o='',v=[])),
  s={
    '':_=>1,
    p:a=>o+=a+` = ${v[a]||0}\n`,
    i:a=>v[a]=-~v[a],
    d:a=>v[a]&&v[a]--,
    a:(n,j)=>s[n]=(u,t,a)=>x(p[!x(p[j+1],0,a,1)+j+2],0,a,1)
  },
  p.map(x=(r,i,w,l,a=r.split(/ +/).slice(l).map(x=>-x?w[x]:x))=>s[a[0]](a[1],i,a)),
  o
)

// TEST

$('#O tr').each(function() {
  var $cells = $(this).find('td')
  var prg = $cells.eq(0).text()
  console.log(prg)
  var output = F(prg)
  $cells.eq(1).text(output)
})
#O td { vertical-align:top; white-space: pre; border: 1px solid #888; font-family:monospace }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<table>
<tr><th>Program</th><th>Outpout</th></tr>
<tbody id=O>  
<tr><td>p _MyVariable_321
p screaming_hairy_armadillo</td><td></td></tr>
<tr><td>i alpaca
p alpaca
i alpaca
p alpaca</td><td></td></tr>
<tr><td>i malamute
p malamute
d malamute    #success
p malamute
d malamute    #failure
p malamute
d akita       #failure
p akita</td><td></td></tr>
<tr><td>a increment_frog_twice
 i frog
 i frog
 d frog
p frog
increment_frog_twice
p frog</td><td></td></tr>
<tr><td>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</td><td></td></tr>
<tr><td>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</td><td></td></tr>
<tr><td>#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  
</td><td></td></tr>
</tbody>
</table>

\$\endgroup\$
2
  • \$\begingroup\$ I've added some more comments to the test program and it looks like they made your code not work. \$\endgroup\$ Aug 13, 2015 at 1:55
  • \$\begingroup\$ @Calvin'sHobbies a first, quick patch \$\endgroup\$
    – edc65
    Aug 13, 2015 at 13:19
3
\$\begingroup\$

C#6, 653 bytes

Here's my entry, amidst a sea of Python...

class P{string[]l;string r="";Dictionary<string,int>v=new Dictionary<string,int>();Dictionary<string,int>s=new Dictionary<string,int>();public string R(string t){l=t.Split('\n');for(int i=0;i<l.Length;i++){var z=l[i].Split(' ');if(z[0]=="a"){s.Add(z[1],i);i+=3;}else E(i, null);}return r;}bool E(int n,string[]p){var z=l[n].Split(' ');var a=z.Skip(1).Select(x=>Char.IsDigit(x[0])?p[int.Parse(x)-1]:x).ToArray();if(a.Length>0&&!v.ContainsKey(a[0]))v[a[0]]=0;if (z[0]=="p")r+=$"{a[0]} = {v[a[0]]}\n";else if(z[0]=="i")v[a[0]]++;else if(z[0]=="d")if(v[a[0]]>0)v[a[0]]--;else return false;else{var y=s[z[0]];return E(y+1,a)?E(y+2,a):E(y+3,a);}return true;}}

Expanded and commented:

class Prindeal
{
    string[] lines;
    string result = "";
    Dictionary<string, int> variables = new Dictionary<string, int>();
    Dictionary<string, int> statements = new Dictionary<string, int>();

    public string Run(string text)
    {
        lines = text.Split('\n');

        for (int i = 0; i < lines.Length; i++)
        {
            // Split on spaces to get the statement and any arguments
            var z = lines[i].Split(' ');

            // Are we defining a new statement?
            if (z[0] == "a")
            {
                // Add to the statements dictionary, step over definition statements
                statements.Add(z[1], i);
                i += 3;
            }
            else
            {
                // Execute the statement
                Execute(i, null);
            }
        }

        return result;
    }

    bool Execute(int lineNumber, string[] parameters)
    {
        // Split on spaces to get the statement and any arguments
        var z = lines[lineNumber].Split(' ');

        // Parse the arguments - if it's a number, get the corresponding 
        // parameter from the calling statement
        var arguments = z.Skip(1).Select(
            x => Char.IsDigit(x[0]) ? 
            parameters[int.Parse(x) - 1] : 
            x)
            .ToArray();

        // If the first argument isn't already in the variables dict, add it
        if (arguments.Length > 0 && !variables.ContainsKey(arguments[0])) variables[arguments[0]] = 0;

        // Print statement, using string interpolation
        if (z[0] == "p")
            result += $"{arguments[0]} = {variables[arguments[0]]}\n";
        // Increment statement
        else if (z[0] == "i")
            variables[arguments[0]]++;
        // Decrement statement
        else if (z[0] == "d")
            if (variables[arguments[0]] > 0)
                variables[arguments[0]]--;
            else
                return false;
        else
        {
            // Get the line number to jump to
            var y = statements[z[0]];

            // Execute A ? B : C
            return Execute(y + 1, arguments) ? Execute(y + 2, arguments) : Execute(y + 3, arguments);
        }

        // If we reach this point, it's from a 'p', 'i' or 'd' statement which has succeeded
        return true;
    }
}

To use it, simply instantiate the class and call the R() method, for example:

string prindealText = new StreamReader("prindeal.txt").ReadToEnd();
Console.WriteLine(new P().R(prindealText));
\$\endgroup\$
3
\$\begingroup\$

Common Lisp, 758 646 619

(progn(set-macro-character #\#(get-macro-character #\;))(setf(readtable-case *readtable*):invert)(#3=defun v(s)(if(boundp s)(eval s)0))(#3# i(s)(set s(1+ (v s))))(#3# d(s)(and(plusp(v s))(set s(1-(v s)))))(#3# p(s)(format t"~A = ~A~%"s(v s)))(defmacro a(n . p)`(#3#,(cadr n)(&rest g)(if,@p)))(#3# k(s)(typecase s(integer`(nth,(1- s)g))(symbol `',s)(t(list*(car s)(mapcar 'k(cdr s))))))(#3# r()(prog(l p q)$(setf p()l(make-string-input-stream(or(read-line()()())(return))))@(when(setf p(read l()()))(push p q)(go @))(if q(return(k(reverse q)))(go $))))(do ((x(r)(r)))((not x))(eval(if(eq(car x)'a)`(,@x,(r),(r),(r))x))))

Put this in file.lisp and call for example sbcl --script file.lisp; input is read from the standard input stream.

This version parses a superset of Prindeal: without not much difficulties, you can access all Common Lisp from a Prindeal source. I consider this a feature of the intepreter.

Commented version

;; copy-readtable is only used during development, so that I do not 
;; mess with my running environment. The real code starts with the
;; progn below, which is superfluous of course inside a let.
(let ((*readtable* (copy-readtable)))

  ;; I use PROGN in the golfed version so that I can have the whole
  ;; program as a unique tree. This allows me to define reader 
  ;; variables like #3=defun in order to gain a few bytes by writing
  ;; #3# instead of defun. Reader variables are removed in
  ;; this human-friendly version.
  (progn
    ;; Let # point to the same reader function as ;
    ;; Of course, ; is still usable as a comment delimiter
    (set-macro-character #\#
                         (get-macro-character #\;))

    ;; :invert does what is necessary to enable case-sensitive reading
    ;; and printing of symbols
    (setf (readtable-case *readtable*) :invert)

    ;; value of symbol, or zero
    (defun v(s)(if(boundp s)(eval s)0))

    ;; increment
    (defun i(s)(set s(1+ (v s))))

    ;; decrement
    (defun d(s)(and(plusp(v s))(set s(1-(v s)))))

    ;; print
    (defun p(s)(format t"~A = ~A~%"s(v s)))

    ;; alias: wrap an "if" inside a "defun".
    ;; YES, that means you can redefine ANY lisp function with "a" !
    ;; A safer version would properly intern symbols in a dedicated package.
    ;;
    ;; Notice the G variable.  We take advantage of the "unhygienic"
    ;; (what a bad adjective) nature of macros to create a context
    ;; where G is bound to the argument list. The same G is referenced
    ;; implicitely later.
    (defmacro a(n . p)`(defun,(cadr n)(&rest g)(if,@p)))

    ;; Canonicalize expressions:
    ;;
    ;; - if s is a symbol, return s quoted. All functions manipulate
    ;; symbols in order to allow the undeclared use of variables. With
    ;; symbols, we can check for boundness.
    ;;
    ;; - if s is an integer, then we are inside an alias definition. The
    ;; integer is replaced by an access to the s'th element of the
    ;; implicit argument list G using (nth (1- s) g). G will be bound
    ;; when the expressions is injected in the defun corresponding to
    ;; the alias, or else an error will be signaled: either because G
    ;; is unbound, or because you defined a variable named G which is
    ;; by construction not a list. Since we do not sanitize properly
    ;; the input, you could bind G globally to a list, but that would be
    ;; nasty.
    ;; 
    ;; - Finally, if s is a list, apply k to all but the first
    ;; elements of s.  The first element is a symbol but we do not
    ;; need to quote it because we want to call the function
    ;; associated with the symbol. Due to the Lisp-2-ness
    ;; of Common Lisp, functions and variables can coexist
    ;; with the same name.
    ;;
    (defun k(s)(typecase s
                 (integer`(nth,(1- s)g))
                 (symbol`',s)
                 (t(list*(car s)(mapcar #'k(cdr s))))))

    ;; Reader function
    (defun r()
      (prog (l ; current line, as an input-stream reading a string
             p ; current read form
             q ; whole line and return value, as a list
             )

         ;; PROG includes an implicit TAGBODY. Below, $ and @ are
         ;; labels for GO statements (gotos).

       $ (setf
          ;; emtpy p
          p ()

          ;; Read a whole line and if we do not fail, build an input
          ;; stream to read from it.
          l (make-string-input-stream
             (or (read-line()()()) ;; try to read a line,
                 (return)          ;; but return from prog if we reach
                                   ;; the end of file.
                 )))
       @ (when (setf p (read l()()))
           ;; Read a lisp expression, put it in p and if p is not nil
           ;; push it into q.  A nil could happen at the end of the
           ;; line or if someone (you know who) inserted an empty list
           ;; in the file being read.
           ;; 
           ;; Thanks to the readtable which now handles comments
           ;; and spaces for us, nothing needs to be done here to
           ;; preprocess the input.

           (push p q) (go @))

         ;; If we read an empty line, q can be nil. In this case, go
         ;; back to $ and read another line. If q is not nil, reverse
         ;; it (we pushed, remember), canonicalize it and return the
         ;; result.
         (if q (return(k(reverse q))) (go $)))
      )

    ;; Read/eval loop.  When reading "(a name)", we read the three
    ;; next lines and append them to the first so that it builds a
    ;; call the the alias definition macro a. Otherwise, just eval x.
    (do((x(r)(r))((not x))
      (eval (if (eq(car x'a))
                `(,@x,(r),(r),(r))
                x)))))

Example

~$ sbcl --script file.lisp < testfile

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

If we replace eval by print in the read/eval loop, then we can see what is being evaluated:

(a 's (i '_) (d '_) (d '_)) 
(a 'f (d '_) (d '_) (d '_)) 
(a 'z (d (nth 0 g)) (z (nth 0 g)) (s)) 
(a 'n (z (nth 0 g)) (i (nth 0 g)) (s)) 
(a 'move (moveH (nth 0 g) (nth 1 g)) (move (nth 0 g) (nth 1 g)) (s)) 
(a 'moveH (d (nth 0 g)) (i (nth 1 g)) (f)) 
(a 'dupe (dupeH1 (nth 0 g) (nth 1 g) (nth 2 g))
   (dupe (nth 0 g) (nth 1 g) (nth 2 g)) (s)) 
(a 'dupeH1 (d (nth 0 g)) (dupeH2 (nth 1 g) (nth 2 g)) (f)) 
(a 'dupeH2 (i (nth 0 g)) (i (nth 1 g)) (s)) 
(a 'copy (z (nth 1 g)) (copyH (nth 0 g) (nth 1 g)) (s)) 
(a 'copyH (dupe (nth 0 g) (nth 1 g) '_copy) (move '_copy (nth 0 g)) (s)) 
(a 'addTo (copy (nth 1 g) '_add) (move '_add (nth 0 g)) (s)) 
(a 'add (z (nth 0 g)) (addH (nth 0 g) (nth 1 g) (nth 2 g)) (s)) 
(a 'addH (addTo (nth 0 g) (nth 1 g)) (addTo (nth 0 g) (nth 2 g)) (s)) 
(a 'mul (mulH1 (nth 0 g) (nth 1 g)) (mulH2 (nth 0 g) (nth 2 g)) (s)) 
(a 'mulH1 (z (nth 0 g)) (copy (nth 1 g) '_mul) (s)) 
(a 'mulH2 (mulH3 (nth 0 g) (nth 1 g)) (mulH2 (nth 0 g) (nth 1 g)) (s)) 
(a 'mulH3 (d '_mul) (addTo (nth 0 g) (nth 1 g)) (f)) 
(a 'mulBy (mul '_mulBy (nth 0 g) (nth 1 g)) (copy '_mulBy (nth 0 g)) (s)) 
(a 'pow (powH1 (nth 0 g) (nth 2 g)) (powH2 (nth 0 g) (nth 1 g)) (s)) 
(a 'powH1 (n (nth 0 g)) (copy (nth 1 g) '_pow) (s)) 
(a 'powH2 (powH3 (nth 0 g) (nth 1 g)) (powH2 (nth 0 g) (nth 1 g)) (s)) 
(a 'powH3 (d '_pow) (mulBy (nth 0 g) (nth 1 g)) (f)) 
(p 'A) 
(p 'B) 
(p 'C) 
(n 'A) 
(n 'B) 
(add 'C 'A 'B) 
(p '____) 
(p 'A) 
(p 'B) 
(p 'C) 
(add 'B 'A 'C) 
(p '____) 
(p 'A) 
(p 'B) 
(p 'C) 
(mul 'd 'B 'C) 
(p '____) 
(p 'd) 
(mulBy 'd 'B) 
(p '____) 
(p 'd) 
(d 'A) 
(mulBy 'd 'A) 
(p '____) 
(p 'd) 
(pow 'A 'C 'B) 
(p '____) 
(p 'A) 
(p 'B) 
(p 'C) 
(pow 'A 'B 'C) 
(p '____) 
(p 'A) 
(p 'B) 
(p 'C) 
(pow 'C 'A 'B) 
(p '____) 
(p 'A) 
(p 'B) 
(p 'C)

Macroexpansion

If we pick the following alias definition:

(a 'powH2 (powH3 (nth 0 g) (nth 1 g)) (powH2 (nth 0 g) (nth 1 g)) (s))

... we can see references to a variable named g which is nowhere to be found in the lexical scope. But after macroexpansion, here is the actual code being evaluated:

(defun powH2 (&rest g)
  (if (powH3 (nth 0 g) (nth 1 g))
      (powH2 (nth 0 g) (nth 1 g))
      (s))) 

Now, g refers to the argument list of the function being defined.

\$\endgroup\$
2
\$\begingroup\$

Python 2, 486 bytes

This is the reference solution which I golfed more (currently -98 bytes).

import sys;sys.setrecursionlimit(2000)
def r(s):
 n=s[0]
 if n in A:f=lambda i:r([s[int(t)]if'0'<t[0]<':'else t for t in A[n][i]]);return f(1+(f(0)or 0))
 k=s[1]
 if'i'<n:print k,'=',V.get(k,0)
 elif'd'<n:V[k]=-~V[k]if k in V else 1
 elif'a'<n:
    if~-(k in V)or V[k]<1:return 1
    V[k]-=1
 else:A[k]=s[2:]
A={};V={};c=filter(bool,([l,l[:l.find('#')]]['#'in l]for l in input().split('\n')))
while c:
 s=c[0].split();c=c[1:]
 if'a'!=s[0]:r(s)
 else:r(['a',s[1]]+map(str.split,c[:3]));c=c[3:]

Changes (that I recall):

  • automatic boolean-integer conversion ([l,l[:l.find('#')]]['#'in l]).
  • set or increment in one statement (V[k]=-~V[k]if k in V else 1)
  • more aliases to longer expressions (k=s[1])
  • no counter in the main loop, clearing the input list instead
  • print automatically adding spaces (print k,'=',V.get(k,0))
  • checking digits 1-9 ('0'<t[0]<':')
  • flipping the return values of r around to save returns
  • removing repetition of slicing and splitting (map(str.split,c[:3])))
\$\endgroup\$
2
\$\begingroup\$

C++, 752 bytes

Thanks to @ceilingcat for some very nice pieces of golfing - now even shorter (yet again!)

This one is C++ - as idiomatic as I could make it.
That means making it more C++-ish and less C-ish.
That also means it is bigger than the equivalent C program.
I think C++ rivals Java for the most verbose standard library.

#import<bits/stdc++.h>
#define a first
#define c(s);else if(B==#s)
#define e(n)(n=regex_replace(n,regex{"^ *(.*?) *(#.*)?$"},"$1")).size()
#define f(n)for(;getline(cin,n),!e(n););
#define i p.top().a
#define j p.size()
#define k n[o[0]]
#define U;using
U namespace std;U g=string;U h=istream_iterator<g>U s=vector<g>;map<g,s>m;map<g,int>n;stack<pair<pair<int,s>,s>>p;main(int v){for(g X,Y,Z;getline(cin,X);)if(e(X))for(p.push({{0,{X,"",""}},{}});bool z=j;){g B;s C,o;copy(h(stringstream(i.second[i.a])>>B),h(),back_inserter(C));for(g x:C)o.push_back((v=atoi(&x[0]))>0?p.top().second[v-1]:x);if(0)c()c(p)cout<<o[0]+" = "<<k<<'\n'c(i)k++c(d)k-=z=k c(a){f(X)f(Y)f(Z)m[o[0]]={X,Y,Z};}else{p.push({{0,m[B]},o});continue;}while(j&&i.a)p.pop();j?i.a-=~!z:0;}}

Try it online!

Below is the original. If anyone can think of a way to make it more idiomatic and shorter at the same time please let me know.

#include <array>
#include <iostream>
#include <map>
#include <regex>
#include <sstream>
#include <stack>

typedef std::vector<std::string> List;
typedef std::pair<std::string, List> Statement;
typedef std::array<std::string, 3> Alias;
typedef std::pair<long, Alias> IndexedAlias;
typedef std::pair<IndexedAlias, List> Item;

std::map<std::string, Alias> aliases;
std::map<std::string, long> variables;
std::stack<Item> stack;
std::regex re("^ *(.*?) *(#.*)?$");

int main()
{
    std::string line, line1, line2, line3;
    while (std::getline(std::cin, line)) // control-Z to exit
    {
        line = std::regex_replace(line, re, "$1");
        if (line.empty()) continue;
        stack.push(Item{ { 0, { { line, "", "" } } }, {} });

        bool flag;
        while (!stack.empty())
        {
            Statement statement;
            std::istringstream ss(stack.top().first.second[stack.top().first.first]);
            ss >> statement.first;
            std::copy(std::istream_iterator<std::string>(ss), std::istream_iterator<std::string>(), std::back_inserter(statement.second));

            List arguments;
            std::transform(std::begin(statement.second), std::end(statement.second), std::back_inserter(arguments),
                [](std::string arg){ int i = atoi(arg.c_str()); return i > 0 ? stack.top().second[i - 1] : arg; });

            flag = true;
            if (statement.first == "")
                ;
            else if (statement.first == "p")
                std::cout << arguments[0] << " = " << variables[arguments[0]] << std::endl;
            else if (statement.first == "i")
                variables[arguments[0]]++;
            else if (statement.first == "d")
                variables[arguments[0]] -= (flag = variables[arguments[0]]);
            else if (statement.first == "a")
            {
                do { std::getline(std::cin, line1); line1 = std::regex_replace(line1, re, "$1"); } while (line1.empty());
                do { std::getline(std::cin, line2); line2 = std::regex_replace(line2, re, "$1"); } while (line2.empty());
                do { std::getline(std::cin, line3); line3 = std::regex_replace(line3, re, "$1"); } while (line3.empty());
                aliases.insert(std::make_pair(arguments[0], Alias{ { line1, line2, line3 } }));
            }
            else
            {
                stack.push(Item{ { 0, aliases[statement.first] }, arguments });
                continue;
            }

            while (!stack.empty() && stack.top().first.first) stack.pop();
            if (!stack.empty()) stack.top().first.first += 1 + !flag;
        }
    }

    std::cout << "-- Variables --" << std::endl;
    std::transform(std::begin(variables), std::end(variables), std::ostream_iterator<std::string>(std::cout, "\n"),
        [](std::map<std::string, long>::value_type pair){ std::ostringstream ss; ss << pair.first << " = " << pair.second; return ss.str(); });
    std::cout << "-- Aliases --" << std::endl;
    std::transform(std::begin(aliases), std::end(aliases), std::ostream_iterator<std::string>(std::cout, "\n"),
        [](std::map<std::string, Alias>::value_type pair){ std::ostringstream ss; ss << pair.first << " = [1]:" << pair.second[0] << " [2]:" << pair.second[1] << " [3]:" << pair.second[1]; return ss.str(); });
    std::cout << "---------------" << std::endl;

    return 0;
}
\$\endgroup\$
0
2
\$\begingroup\$

Uiua (tentative), 468 characters

Note that due to a bug in the interpreter, there is a spurious error preventing proper execution. Once the bug is fixed, I will update this answer to reflect that. Until then, I'm leaving the (tentative) in the heading.

p←⊂↯→(⊟∶□0□,)¬/+≡(|2≅!)→(↯→∶⧻).≡⊢.→(.!⊡1)!→→(⊡2)⊡1,∶
;∧(|2!⊡∶(⊂□1⊟□&p/+∺(×≅→∶∷!⊢∶⊡1.),∶&pf⊂∶" = ",p)_(⊂□1⊟□∺([∶∷□]+→(∶→≅.)!⊡1∶!⊢.)p)_(⊂⊟→□→≡(⍜'⊡1⍜!'↥0)/×≡(≥0!⊡1).∺([∶∷□]-∶→(∶→≅.)!⊡1∶!⊢.)p)_(|2↬2∶□/(⊂⊂∶@ ∷!)⊡-∶2!⊢,∶↬2∶□/(⊂⊂∶@ ∷!)⊢,∶→→;∺(|2⍜'↘1∺(|2□⍣(!⊡parse)(→;;)!)⊜□≠,@ !)→,↘1⊡∶!⊡2,⊗∶≡⊢!⊡2,⊢,)⊗∶["p""i""d"]!⊢,∶⊜□≠@ .!∶)⊂{1[{"1"0}]}□→(▽¬∵(|1↥=@ ∶=@a.⊢!).)≡(⍜'↘1(∵⍜!(↘1))⍜⊢(⍜!(↘2)))⊜·▽∵(4;).+1⇡÷4⧻.▽∵(|1↥=@ ⊢∶≅"a ".↙2!)..▽↧1∵⧻.∵⍜!(⇌▽\↥×≠@\r∶≠@ ..⇌▽=0\+=@#.)⊜□≠@\n.&ru@\00

This is my first Uiua program of more than a couple dozen characters, so I'm sure there's lots of places I could golf it more effectively. It was a good opportunity to practice the language, though :)

Non-minified version

# preexec state_alias_array command -> state_array command_arg alias_array
preexec ← (
  ⊡1,∶ # put state on top
  !→→(⊡2) # get aliases (for later)
  →(.!⊡1) # get variable name (put two copies on stack for later)
  ≡⊢. # get list of current variable names 
  ¬/+≡(|2 ≅!) →(↯→∶⧻). # test if we need to add a new variable
  ⊂↯→(⊟∶□0□,) # add variable if necessary
)

# exec<T> state_alias_array command -> state_alias_array
execp ← (
  preexec
  &pf⊂∶" = ", # print "varName = "
  &p/+∺(×≅→∶∷!⊢∶⊡1.),∶ # print "varValue\n"
  ⊂□1⊟□
)
execi ← (
  preexec
  ∺([∶∷□]+→(∶→≅.)!⊡1∶!⊢.) # increment the relevant variable
  ⊂□1⊟□ # reform state_alias_array
)
execd ← (
  preexec
  ∺([∶∷□]-∶→(∶→≅.)!⊡1∶!⊢.) # decrement the relevant variable
  /×≡(≥0!⊡1). # check if <0
  →≡(⍜'⊡1⍜!'↥0) # reset back up to 0
  ⊂⊟→□ # reform state_alias_array
)
execcall ← (|2
  ⊢, # get alias name
  ⊗∶≡⊢!⊡2, # get index of call
  ↘1⊡∶!⊡2, # get subcommands
  →,
  ∺(|2
    ⊜□≠,@ ! # split box"thing 1 2" into [box"thing" box"1" box"2"]
    ⍜'↘1∺(|2 □⍣(!⊡parse)(→;;)!) # turn the 1 and 2 into first and second arguments
  )
  →→; # original call no longer necessary
  □/(⊂⊂∶@ ∷!)⊢,∶ # join [box"i" box"dog"] into box"i dog"
  ↬2∶ # call first
  ⊡-∶2!⊢,∶ # select second or third based on success
  □/(⊂⊂∶@ ∷!) # join [box"i" box"dog"] into box"i dog"
  ↬2∶
)

execline ← (|2
  ∶⊜□≠@ .!∶ # split command
  !⊡∶execp_execi_execd_execcall⊗∶["p""i""d"]!⊢, # dispatch correct command
)

&ru@\00

⊜□≠@\n. # split lines
∵⍜!(
  ▽=0\+=@#. # remove comments
  ⇌▽\↥×≠@\r∶≠@ ..⇌ # remove trailing whitespace
)
▽↧1∵⧻. # remove blank lines

▽∵(|1 ↥=@ ⊢∶≅"a ".↙2!).. # get alias definitions
⊜· ▽∵(4;).+1⇡÷4⧻. # separate out each alias
≡(
  ⍜⊢(⍜!(↘2)) # remove alias decl
  ⍜'↘1(∵⍜!(↘1)) # remove indentation
)
→(▽¬∵(|1 ↥=@ ∶=@a.⊢!).) # remove alias definitions from command list
⊂{1 [{"123illegalvariable" 0}]}□ # group last success, state array and alias array
∧execline # exec all instructions
;
\$\endgroup\$
1
\$\begingroup\$

Python 3, 1322 bytes

Golfed:

import re,sys;sys.setrecursionlimit(2000);F,L=filter,list
class P:
 N,O,F=0,{},{}
 def __init__(S,c):
  S.B,S.E={"p":S.P,"i":S.I,"d":S.D,"a":S.L},dict(enumerate(F(None,[i.split('#')[0].rstrip()for i in c.splitlines()])))
  while S.N in S.E:S.X(S.E[S.N])
 def V(S, v, y, z=0):
  if re.match("[\w_][\d\w_]*",v):
   if not v in y:
    if z is not None:y[v]=z
    else:return False
   return True
  return False
 def A(S):S.N+=1
 def P(S,v):
  if S.V(v,S.O):print("{0} = {1}".format(v, S.O[v]));return True
  return False
 def I(S,v):
  if S.V(v, S.O):S.O[v]+=1;return True
  return False
 def D(S,v):
  if S.V(v,S.O)and S.O[v]>0:S.O[v]-=1;return True
  return False
 def L(S,v):
  e=[]
  if S.V(v,S.F,e):
   for i in range(3):S.A();e.append(S.E[S.N].lstrip())
   return True
  return False
 def C(S,c,v):
  def R(Z,v):
   for i in re.findall("\s(\d+)", Z):Z=Z.replace(" %s"%i," %s"%v[int(i)-1])
   return Z
  Q,m,f=map(lambda l:R(l,v),S.F[c])
  if S.X(Q,False):return S.X(m,False)
  return S.X(f,False)
 def X(S,Z,C=True):
  u=re.match("\s?([\w_][\d\w_]*)\s?([\w_][\d\w ]*)?",Z)
  if u:
   c,v=map(lambda i:''if i is None else i,u.groups());v=L(F(None,v.split(' ')))
   if S.V(c,S.F,None):
    T=S.C(c, v)
    if C:S.A()
   elif S.V(c,S.B,None):
    T=S.B[c](*v)
    if C:S.A()
   else:return False
   return T
  return False

Ungolfed:

import re

class Prindeal:
    iline = 0
    local = {}
    udef = {}
    content  = {}

    def __init__(self, c):
        self.built = {
            "p": self.print,
            "i": self.increment,
            "d": self.decrement,
            "a": self.alias,
        }
        self.content = dict(enumerate(filter(None, [i.split('#')[0].rstrip()for i in c.splitlines()])))
        while self.iline in self.content:
            self.execute_line(self.content[self.iline])

    def validate_name(self, varname, stack, default=0):
        if re.match("[\w_][\d\w_]*", varname):
            if not varname in stack:
                if default is not None:
                    stack[varname] = default
                else:
                    return False
            return True
        return False

    def advance_stack(self):
        self.iline += 1

    def print(self, varname):
        if self.validate_name(varname, self.local):
            print("{0} = {1}".format(varname, self.local[varname]))
            return True
        return False

    def increment(self, varname):
        if self.validate_name(varname, self.local):
            self.local[varname] += 1
            return True
        return False

    def decrement(self, varname):
        if self.validate_name(varname, self.local) and self.local[varname] > 0:
            self.local[varname] -= 1
            return True
        return False

    def alias(self, aliasname):
        indexed_lines = []
        if self.validate_name(aliasname, self.udef, indexed_lines):
            for i in range(3):
                self.advance_stack()
                indexed_lines.append(self.content[self.iline].lstrip())
            return True
        return False

    def execute_alias(self, cmd, variables):
        def parse_args(line, variables):
            for i in re.findall("\s(\d+)", line):
                line = line.replace(" %s" % i, " %s" % variables[int(i) - 1])
            return line
        init, success, failure = map(lambda l: parse_args(l, variables), self.udef[cmd])
        if self.execute_line(init, False):
            return self.execute_line(success, False)
        return self.execute_line(failure, False)

    def execute_line(self, line, cont=True):
        valid_execution = re.match("\s?([\w_][\d\w_]*)\s?([\w_][\d\w ]*)?", line)
        if valid_execution:
            cmd, variables = map(lambda i: '' if i is None else i, valid_execution.groups())
            variables = list(filter(None, variables.split(' ')))
            if self.validate_name(cmd, self.udef, None):
                temp = self.execute_alias(cmd, variables)
                if cont:
                    self.advance_stack()
            elif self.validate_name(cmd, self.built, None):
                temp = self.built[cmd](*variables)
                if cont:
                    self.advance_stack()
            else:
                return False
            return temp
        return False

Usage:

P(c)

Where c is the text content.

Examples:

Single-line strings are accepted:

  • P("p cat")
  • P("p dog\ni dog\np dog")

Multi-lined strings are also accepted:

P("""
p dog
i dog
p dog
""")

Or:

P("""p dog
i dog
p dog""")

Etc.

Notes:

This works correctly for all test cases, but reaches the recursion limit on:

pow C A B   #C = A ^ B = 9 ^ 3 = 729

Hence the sys.setrecursionlimit(2000).

\$\endgroup\$
3
  • 1
    \$\begingroup\$ It'll use up some bytes but couldn't you use sys.setrecursionlimit() to get this working properly with the pow alias? \$\endgroup\$
    – Corwin
    Aug 12, 2015 at 13:05
  • \$\begingroup\$ I could, but OP stated that languages like Python (that have recursion limits) are accepted as-is. However, I will add the fix if requested by OP. @Corwin \$\endgroup\$
    – Zach Gates
    Aug 12, 2015 at 13:45
  • \$\begingroup\$ Fair enough. Missed that in the spec. @ZachGates \$\endgroup\$
    – Corwin
    Aug 12, 2015 at 13:47
1
\$\begingroup\$

Haskell, 1009

I did my best to golf it; my ungolfed code consisted of over 3,000 characters. At this point I can't remember what all the functions are doing so golfing it more mean guessing what will break it and what won't.

import qualified Data.Map as M
import Control.Monad.State.Lazy
import Data.List
type A=M.Map String
data P=P(A Int)(A([String]->StateT P IO Int))
a f=evalStateT f(P M.empty$M.fromList[("i",\(b:_)->(+1)%b),("d",\(b:_)->pred%b),("p",\(b:_)->i b>>= \v->liftIO(putStrLn$b++"="++show v)>>q 1)])
e(k:l)=do{(P v a)<-get;put.P v$M.insert k(m l)a;q 1}
g t s f= \a->t a>>= \b->if b>0then s a else f a
f%k=f<$>i k>>= \v->if v<0then k#0>>q 0else k#v>>q 1
i k=get>>= \(P v _)->q$M.findWithDefault 0 k v
k#v=get>>= \(P b a)->put$P(M.insert k v b)a
l k=get>>= \(P _ a)->q$a M.!k
f s=let(f:a)=r s in($a)<$>l f>>=id
m(t:s:f:_)=g(k t)(k s)(k f)
k s=let(f:b)=r s in\a->($(map((\y z->if all(\c->c>'/'&&c<':')z then y!!(read z-1)else z)a)b))<$>l f>>=id
n=dropWhileEnd(==' ').takeWhile(not.(=='#')).dropWhile(==' ')
o[]=[]
o(l:ls)|(head.r$l)=="a"=(l:take 3 ls):(o$drop 3 ls)|1>0=[l]:o ls
p s|length s>1=e$(n.tail.head$s):tail s|1>0=f.head$s
q=return
main=join$a.(\s->mapM_ p(o.filter(not.null).map n.lines$s))<$>getContents
r=words
\$\endgroup\$
1
\$\begingroup\$

Python - 695 688 bytes

def p(v):print v,"=",w.get(v,0)
def i(v):w[v]=w.get(v,0)+1
def d(v):
 if v in w:
<TAB>w[v]-=1
<TAB>if not w[v]:del w[v]
 else:return 1
def a(n,b,d,h):
 def g(*a):
<TAB>i=1;f=b;s=d;t=h
<TAB>for v in a:v=q+v+q;k=q+j(i)+q;f=c(f,k,v);s=c(s,k,v);t=c(t,k,v);i+=1
<TAB>y=u(t,e)if u(f,e)else u(s,e);i=1;return y
 e[n]=g
q="'";w=x={};u=eval;e={'a':a,'d':d,'i':i,'p':p};import sys;l=sys.stdin.readlines();r="";j=str;c=j.replace;sys.setrecursionlimit(2000)
for h in l:
 h = h.strip()
 if not h:continue
 l = h.split();f=l[0];n=f+"("
 if "#" in f:continue
 for g in l[1:]:
<TAB>b=g.find("#")+1
<TAB>if b:g=g[:b-1]
<TAB>if g:n+="'%s',"%g
<TAB>if b:break
 if x:x-=1;d+='"%s)",'%n
 else:x=(f=="a")*3;d=n
 if not x:d+=")\n";r+=d
exec r in e

<TAB> is a literal tab character.

\$\endgroup\$
0
\$\begingroup\$

APL (Dyalog Unicode), 449 422 388 383 bytes

This is a somewhat optimized APL version of a program to run the Prindeal code. It assumes that the index origin is 0 (⎕IO←0). The Prindeal program to run is in an enclosed string array P.


z
L←0
V←N←F←M←⍬
r←{⍬≡h←⍸⎕D∊⍨⊃¨t←↑⍵:t⋄t[h]←⍺[⍎¨t[h]]⋄t}
R←{⍵/⍨∊0≠⍴∘∊¨⍵}{' '(≠⊆⊢)⍵/⍨~∨\'#'=⍵}∘∊¨P
k:e↑L⌷R
L+←1
→k/⍨L<⍴R

{y}←e c;x
y←1
→((⊃c)∘≡∘,¨'pida')/p i d a
y←e c r x⌷⍨2-e c r 0⌷x←↑F[M⍳0⌷c]
→0
p:N[x],’=‘,2⊥⌽↑V[x←g c]
→0
i:V[x]←⊂{⍬≡⍵:1⋄⊃⍵:0,∇1↓⍵⋄1,1↓⍵}↑V[x←g c]
→0
d:→0/⍨~y←(,0)≢m←↑V[x←g c]
V[x]←⊂m↓⍨-(1=⍴m)⍱⊃⌽m←{⍬≡⍵:⍬⋄~⊃⍵:1,∇1↓⍵⋄0,1↓⍵}m
→0
a:M←M,1⌷c
F←F,⊂R[L+1+⍳3]
L+←3

x←g c
x←N⍳1⌷c
→0/⍨x<⍴N
N←N,1⌷c
V←V,⊂,0

The code length was a bit less until I reread the specification that requires arbitrary precision integers. I added code to provide this functionality although I'd hate to wait around for a Prindel program that requires this feature to execute. Dialog APL does have some prewritten routines ('big') to provide this functionality, However, I felt it would be a bit of a cheat to use all that extra 'hidden' code and call it an inherent part of the language.

In any case, the program consists of eight basic functions as follows:

 z - this function initializes variables, strips comments, trailing blanks and empty lines; then loops through the Prindeal program code.

 e - this executes a Prindeal statement either an inherent statement (p,i,d,a) or a defined alias. Recursive.

 g - gets a reference to a variable value (defines if as-of-yet undefined)

 r - at execution time, resolves numerical reference to arguments in alias functions

Comments and improvements are welcomed. I first dabbled in APL in the late 60s and early 70s. Life took me in another direction, but apparently I never lost my interest in the language. I recently took a look at the state of the language (via Dialog) and found that what was a powerful language back in the day had only grown in complexity and capability. I'm using some of these code golf challenges to introduce me to the newer (to me) language features and have a bit of fun ... lifelong learning.

In any case, here is the output from the test program:


     z
 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

```
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.