| Bytes | Lang | Time | Link |
|---|---|---|---|
| 671 | C++ | 150825T220441Z | Jerry Je |
| 383 | APL Dyalog Unicode | 231001T201924Z | boltcapt |
| 468 | Uiua tentative | 231003T235918Z | Pseudo N |
| 688 | Python | 160406T200749Z | The Fift |
| nan | Common Lisp | 150812T161027Z | coredump |
| 1009 | Haskell | 150813T145048Z | ankh-mor |
| 273 | JavaScript ES6 | 150812T194133Z | edc65 |
| 136 | Pyth | 150813T023108Z | isaacg |
| 653 | C#6 | 150812T160558Z | Sok |
| 373 | Python 2 | 150812T073152Z | Calvin |
| 328 | Python 3 | 150812T150618Z | Sp3000 |
| 1322 | Python 3 | 150812T105133Z | Zach Gat |
| 486 | Python 2 | 150812T110857Z | PurkkaKo |
C++, 671 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 c(s);else if(B==#s)
#define e(n)(n=regex_replace(n,regex{"^ *(.*?) *(#.*)?$"},"$1"))[0]
#define i(x)get<x+0>(p[j-1])
#define k n[o[0]]
#define U;using
int j,v,z;U namespace std;U g=string;U h=istream_iterator<g>U s=deque<g>;map<g,s>m;map<g,int>n;tuple<int,s,s>p[999];main(){g Y;for(s X(3);getline(cin,Y);)if(e(Y))for(p[j++]={0,{Y,"",""},{}};z=j;){g B;s C,o;copy(h(stringstream(i(1)[i()])>>B),h(),back_inserter(C));for(g x:C)o.push_back((v=atoi(&x[0]))>0?i(2)[v-1]:x);if(0)c()c(p)cout<<o[0]+" = "<<k<<'\n'c(i)k++c(d)k-=z=!!k c(a)for(g&y:X)for(;getline(cin,y),m[o[0]]=X,!e(y););else{p[j++]={0,m[B],o};continue;}while(j*i())j--;i()-=!!j*~!z;}}
Below is the original.
#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;
}
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
```
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
;
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.
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.
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
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>
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
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
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));
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 :)
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).
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).
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
printautomatically adding spaces (print k,'=',V.get(k,0))- checking digits 1-9 (
'0'<t[0]<':') - flipping the return values of
raround to savereturns - removing repetition of slicing and splitting (
map(str.split,c[:3])))