A small language deserves a small interpreter
Ruby, 182 bytes
$h=Hash.new 0
def r(c)c.scan(/(([^!?^<>]*)(<(\g<1>*)>|[!?^]))/){$4?($1=~/(.*?)<(.*)>/
($h[$1]-=1;r$2)while$h[$1]>0):$3<?"?p($h[$2]):$h[$2]+=$3<?@?STDIN.gets.to_i:
1}end
r IO.read *$*
Try it like this:
$ cat code
a?b<>c<>a<c^c^c<b^>>b!
$ ruby lynn.rb code
3 <-- input
6 <-- output
How it works
The r
function tokenizes an input string and executes each token:
def r(c)
c.scan(/(([^!?^<>]*)(<(\g<1>*)>|[!?^]))/){
...
}
end
We look for some variable name $2
matching [^!?^<>]*
, followed by either
<...>
where...
matches zero or more programs (\g
is recursion), in which case$4
isn'tnil
- A
!
,?
, or^
character, captured by$3
, in which case$4
isnil
.
Then the logic for executing a token is quite simple when indenting it a bit:
$4 ? ( # If it's a loop:
$1 =~ /(.*?)<(.*)>/ # Re-match token*
($h[$1]-=1; r $2) while $h[$1] > 0 # Recurse to run loop
) : # Else:
$3 < ?" # If it's an !:
? p($h[$2]) # Print the var
: $h[$2] += # Else, increment it by:
$3 < ?@ # If it's a ?:
? STDIN.gets.to_i # User input
: 1 # Else: 1
* There's an oniguruma bug, I think, that keeps me from simply using $3 here.