15
\$\begingroup\$

Interpret Volatile

Volatile is a stack-based esolang made by A_/a'_'/A that only has 8 instructions and is turing complete. However, it is also non-deterministic... meaning that programs don't always give the same output. Your task is to interpret this language.

Language specs

Taken from the esolangs page:

~: Push a random integer in any range of integers. Minimum range of 0 through 32768

+: Pop 2 values and push the sum of the 2 values

-: Like +, but subtracts

*: Multiply

/: Divide. 0-division will result in an error.

:: Duplicate the top of the stack

.: Output the top of the stack without popping it


(...): Execute ... inside a while loop when the top of the stack is not 0

Everything else is disregarded

Input

Note that these programs may randomly fail

~:/::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.~:/:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.~:/::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.~:/::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.~:/:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.~:/::::::::::::::::::::::::::::::::::::::::::::++++++++++++++++++++++++++++++++++++++++++++.~:/::::::::::::::::::::::::::::::::++++++++++++++++++++++++++++++++.~:/:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.~:/:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.~:/::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.~:/::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.~:/::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.~:/:::::::::::::::::::::::::::::::::+++++++++++++++++++++++++++++++++.~:/::::::::::++++++++++.

~:-.~:/+.(~:/+.)

~:-:/

Output

73 102 109 109 112 45 33 120 112 115 109 101 34 11

0 1 2 3 4 5 6 7 8 9 ...

<Any Error Message>

More examples, as well as a reference implementation (use the second one, found under (Another) python 3 interpreter) can be found at https://esolangs.org/wiki/Volatile

Scoring

This is code-golf, so shortest answer in bytes wins

Leaderboards

Here is a Stack Snippet to generate both a regular leaderboard and an overview of winners by language.

To make sure that your answer shows up, please start your answer with a headline, using the following Markdown template:

# Language Name, N bytes

where N is the size of your submission. If you improve your score, you can keep old scores in the headline, by striking them through. For instance:

# Ruby, <s>104</s> <s>101</s> 96 bytes

If there you want to include multiple numbers in your header (e.g. because your score is the sum of two files or you want to list interpreter flag penalties separately), make sure that the actual score is the last number in the header:

# Perl, 43 + 2 (-p flag) = 45 bytes

You can also make the language name a link which will then show up in the leaderboard snippet:

# [><>](http://esolangs.org/wiki/Fish), 121 bytes

var QUESTION_ID=191573;
var OVERRIDE_USER=78850;
var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",COMMENT_FILTER="!)Q2B_A2kjfAiU78X(md6BoYk",answers=[],answers_hash,answer_ids,answer_page=1,more_answers=!0,comment_page;function answersUrl(d){return"https://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+d+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function commentUrl(d,e){return"https://api.stackexchange.com/2.2/answers/"+e.join(";")+"/comments?page="+d+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+COMMENT_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(answer_page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(d){answers.push.apply(answers,d.items),answers_hash=[],answer_ids=[],d.items.forEach(function(e){e.comments=[];var f=+e.share_link.match(/\d+/);answer_ids.push(f),answers_hash[f]=e}),d.has_more||(more_answers=!1),comment_page=1,getComments()}})}function getComments(){jQuery.ajax({url:commentUrl(comment_page++,answer_ids),method:"get",dataType:"jsonp",crossDomain:!0,success:function(d){d.items.forEach(function(e){e.owner.user_id===OVERRIDE_USER&&answers_hash[e.post_id].comments.push(e)}),d.has_more?getComments():more_answers?getAnswers():process()}})}getAnswers();var SCORE_REG=function(){var d=String.raw`h\d`,e=String.raw`\-?\d+\.?\d*`,f=String.raw`[^\n<>]*`,g=String.raw`<s>${f}</s>|<strike>${f}</strike>|<del>${f}</del>`,h=String.raw`[^\n\d<>]*`,j=String.raw`<[^\n<>]+>`;return new RegExp(String.raw`<${d}>`+String.raw`\s*([^\n,]*[^\s,]),.*?`+String.raw`(${e})`+String.raw`(?=`+String.raw`${h}`+String.raw`(?:(?:${g}|${j})${h})*`+String.raw`</${d}>`+String.raw`)`)}(),OVERRIDE_REG=/^Override\s*header:\s*/i;function getAuthorName(d){return d.owner.display_name}function process(){var d=[];answers.forEach(function(n){var o=n.body;n.comments.forEach(function(q){OVERRIDE_REG.test(q.body)&&(o="<h1>"+q.body.replace(OVERRIDE_REG,"")+"</h1>")});var p=o.match(SCORE_REG);p&&d.push({user:getAuthorName(n),size:+p[2],language:p[1],link:n.share_link})}),d.sort(function(n,o){var p=n.size,q=o.size;return p-q});var e={},f=1,g=null,h=1;d.forEach(function(n){n.size!=g&&(h=f),g=n.size,++f;var o=jQuery("#answer-template").html();o=o.replace("{{PLACE}}",h+".").replace("{{NAME}}",n.user).replace("{{LANGUAGE}}",n.language).replace("{{SIZE}}",n.size).replace("{{LINK}}",n.link),o=jQuery(o),jQuery("#answers").append(o);var p=n.language;p=jQuery("<i>"+n.language+"</i>").text().toLowerCase(),e[p]=e[p]||{lang:n.language,user:n.user,size:n.size,link:n.link,uniq:p}});var j=[];for(var k in e)e.hasOwnProperty(k)&&j.push(e[k]);j.sort(function(n,o){return n.uniq>o.uniq?1:n.uniq<o.uniq?-1:0});for(var l=0;l<j.length;++l){var m=jQuery("#language-template").html(),k=j[l];m=m.replace("{{LANGUAGE}}",k.lang).replace("{{NAME}}",k.user).replace("{{SIZE}}",k.size).replace("{{LINK}}",k.link),m=jQuery(m),jQuery("#languages").append(m)}}
body{text-align:left!important}#answer-list{padding:10px;float:left}#language-list{padding:10px;float:left}table thead{font-weight:700}table td{padding:5px}
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="https://cdn.sstatic.net/Sites/codegolf/primary.css?v=f52df912b654"> <div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table> </div><div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr></thead> <tbody id="answers"> </tbody> </table> </div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td><a href="{{LINK}}">{{SIZE}}</a></td></tr></tbody> </table> <table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td><a href="{{LINK}}">{{SIZE}}</a></td></tr></tbody> </table> 

\$\endgroup\$
10
  • 2
    \$\begingroup\$ Is division supposed to round toward 0? \$\endgroup\$
    – Grimmy
    Sep 10, 2019 at 13:15
  • \$\begingroup\$ Does ~ push an integer, or any number? \$\endgroup\$
    – mbomb007
    Sep 10, 2019 at 13:33
  • 1
    \$\begingroup\$ Also, depending on the implementation, this might not really be non-deterministic. Sure, the esolang page claims it is, but that's only because of the way listed to push 1, which might divide by zero. ~ must be able to push zero, otherwise it's deterministic. Also, the RNG must always be able to return zero after any number of zeroes in a row. \$\endgroup\$
    – mbomb007
    Sep 10, 2019 at 13:42
  • 2
    \$\begingroup\$ Shouldn't that second input ~:-.~:/+.(~:/+.) start from 0 1 2 ... instead of 1 2 3 ...? The ~:-. would result in 0 which it outputs. Those interpreters on the Esolang page seem to confirm this (here the second). \$\endgroup\$ Sep 10, 2019 at 14:56
  • 2
    \$\begingroup\$ Suggestion to add test case: ~:-.(~:/+.) \$\endgroup\$
    – Night2
    Sep 11, 2019 at 10:32

13 Answers 13

7
\$\begingroup\$

05AB1E, 35 bytes

"~:/.()"”žGÝΩ DŠéõq}÷ = [D_# }”#‡.V

Try it online!

Transpiles Volatile code to 05AB1E, then evals it. *, + and - can be left as-is. :, ., and ) have direct one-byte equivalent. The other commands take a few bytes each. Unfortunately, 05AB1E does not crash on division by 0, so this is instead implemented by a conditional "quit if top of stack == 0".

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Does this work correctly for the loop. If I input the second test case it seems to ouput 0 and 1 correctly before the loop, but then it starts outputting the Volatile program (the implicit input) itself. I like that D Doingõq dictionary string for the if-statement, btw! :) \$\endgroup\$ Sep 10, 2019 at 15:03
  • 1
    \$\begingroup\$ @KevinCruijssen the example seems to assume the loop is a while peek, but in the reference interpreter it's a while pop. The example can be fixed by adding some : (TIO). Alternatively, my code can be changed to a while peek by adding a D. \$\endgroup\$
    – Grimmy
    Sep 10, 2019 at 15:18
  • 1
    \$\begingroup\$ Both reference interpreters from the Esolang still output 0 1 2 3 ..., though. I haven't looked at their source code, but just tried both of them. \$\endgroup\$ Sep 10, 2019 at 15:19
  • 1
    \$\begingroup\$ @KevinCruijssen Oh, the function is named pop but is actually a peek. Maximally confusing. I added the D in my code. \$\endgroup\$
    – Grimmy
    Sep 10, 2019 at 15:22
  • \$\begingroup\$ Lol.. Confusing builtin names reminds me of Java's replace vs replaceAll (which both replace all occurrences, but the replaceAll uses regexes and the other one not). xD \$\endgroup\$ Sep 10, 2019 at 15:24
4
\$\begingroup\$

Julia 1.0, 334 bytes

a\b=push!(a,b)
√a=pop!(a)
v(q,s=[],l=0)=(i=1;
for c in q
!r=c==r
i+=1
!')' ? (l<1 && break;l-=1) :
!'(' ? (while s[end]!=0 v(q[i:end],s) end;l+=1) :
l>0 ? continue :
!'~' ? s\rand(Int) : 
!'+' ? s\(√s+√s) :
!'-' ? s\(√s-√s) :
!'*' ? s\(√s*√s) :
!'/' ? s\(√s÷√s) :
!':' ? s\s[end] :
!'.' ? print(s[end]," ") : 0
end)

My first "interpreter" of any type, it was easier than I expected. I did some basic golfing, but there is probably room for more. I made it print a space after the output for . to mach to example output. The ungolfed version is in the header at the TIO link. Example usage v("~:-:/").

+41 bytes to fix the bug Night2 pointed out by adding a loop counter. Now I see why transpiling is a good option. A good test case is ~:-.(~:/+.)(~:/+.())~:-. with expected output 0 0

Try it online!

\$\endgroup\$
1
3
\$\begingroup\$

Runic Enchantments, 266 264 bytes

DS͗{r;'ui[0[0y̤<<<<<?+f2,;$"!0/"?*7≠0:S͗\
RS͗}:'~=?!\:':=?!\:'+=?!\:'-=?!\:'/=?!/:'.=?!\:'*=?!\:';=?!;:')≠3*?04B͍:'(=?!S͗
U/lA`R耀`S͗/?7  :S͗/?+f1+S͗/?3  -S͗/?3     $ '$:S͗/?7  *S͗/
U\m(d*?"SO!"$;
{:'(=?!\:')=?!\R
~/?)0l{͗/?8 {͗l}͗/U
 \}͗21B͍

Try it online!

Due to limitations built into Runic, it can only support program lengths (and stack size) of ~501. Programs that are too large will simply fail. If the stack grows too large, it will error with SO! (wasn't required, but was better than silent termination; cost 24 bytes). If the program attempts to divide by 0, it will print /0!.

Errors are appended to the end of standard output as Runic has no way of writing to STDERR.

This version will support arbitrarily long programs, but is still limited to a stack of ~90 (and thus errors out on the 2nd result of the first test program) and has not been golfed very well (the increase in command length between S͗}: and S͗}͍:0%: required some additional spacing to get sections to line up, but that extra space also allowed for more < for a larger max stack size).

Alternatively, this program will avoid ~ generating a zero and the program will terminate after 1 million execution steps (a safeguard against infinite loops built into the Runic interpreter). Also includes a few bytes to skip over excess NOP space and run a little longer.

  1. Stack oversize fizzling occurrs at (IP mana+10) and the check I put in for Volatile's stack is sizeof(stack) < mana and there are 5 IPs that merge and combine their mana (50 initial). Increasing that value to the true limit (the +10) would cost another 2 bytes and I left the logic golfy instead of accurate.

Explanation

Volatile flow

  1. Program begins in the top-center blue area along the <<<<< and the five IPs merge together at the y
  2. IP moves to the left, setting up the stacks and reading input and moves to the cyan section
  3. IP moves to the right down this line checking the top char on the stack for what command it is. This line continues to the right further than is visible. when the correct instruction is found it executes the code on the line below to the left (skipping over other sections to return back to the cyan via the blue).
  4. Magenta section is executed during ~ or : commands to error on Stack Overflow, yellow section is skipped if the stack is not overfull and returns via wraparound to the dark blue.
  5. Far off to the right when the ) is found the program branches to the red section and moves to the right.
  6. This section rotates the command stack to the right until a ( is found (proceed green).
  7. For each additional ) is found (proceed orange), the stack depth stack is bumped and when a ( is found, the stack depth stack is popped once (proceed dark green and orange re-entry)
  8. If the depth stack is empty continue following green to the B and return to cyan to main parsing loop, otherwise wrap via orange->yellow->red (re-entering the loop-reset loop).
  9. The purple in the top right handles division, if the value to divide by is 0, the brown section handles the error and termination. Re-enters main parsing loop by skipping over the cyan section.
\$\endgroup\$
3
\$\begingroup\$

Lua, 323 bytes

l,s=load,{}u=l't=...s[#s+1]=t'o=l'n=t s[#s]=_ t=s[#s]return n'math.randomseed(os.time())l((...):gsub('[^-*+/:.()~]',''):gsub('.',{['~']='u(math.random(1e5))',['+']='u(o()+o())',['-']='u(o()-o())',['*']='u(o()*o())',['/']='u(0~=s[#s-1]and o()//o()or-_)',[':']='u(t)',['.']='print(t)',['(']='while 0~=t do ',[')']='end '}))()

Try it online!

Transpiles the Volatile code into Lua. Note that s represents the stack itself, u pushes onto the stack, o pops off the stack, and t represents the value at the top of the stack.

Volatile Lua
~ u(math.random(1e5))
+ u(o()+o())
- u(o()-o())
* u(o()*o())
/ u(0~=s[#s-1]and o()//o()or-_)
: u(t)
. print(t)
( while 0~=t do
) end
\$\endgroup\$
2
\$\begingroup\$

PHP, 196 bytes

eval(strtr($argn,[':'=>($v='$a[]=').$e='end($a);','~'=>$v.'rand();','+'=>($q=$v.$p='array_pop($a)')."+$p;",'-'=>"$q-$p;",'*'=>"$q*$p;",'/'=>"$q/$p;",'.'=>"echo' ',$e",'('=>"for(;$e){",')'=>'}']));

Input 1: Try it online!

Input 2 (0, 1, 2, ...): Try it online!

Input 3 (Division by zero error): Try it online!

Just translates the code to PHP and evaluates it!

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

JavaScript (V8),  178 172  171 bytes

Transpiles to JS. May throw either Z is not defined or x is not defined if the code attempts to do something bad.

s=>eval(s.replace(/./g,c=>`S.push(${c>'}'?'x=Math.random()*1e5|0':c>'9'?'x':c=='.'?');print(x':c<')'?');while(x){(0':c<'*'?')}(0':`1/(x${c}=S.pop(S.pop()))?x:Z`});`,S=[]))

Try the 1st program online!

Try the 2nd program online!

Try the 3rd program online!

How?

Each instruction is transpiled to S.push(, followed by a specific pattern, followed by );.

We have to test the division by zero explicitly because JS doesn't give the slightest damn about such a harmless operation. :-p

 char. | JS code
-------+--------------------------------------
   ~   | S.push( x=Math.random()*1e5|0 );
   +   | S.push( 1/(x+=S.pop(S.pop()))?x:Z );
   -   | S.push( 1/(x-=S.pop(S.pop()))?x:Z );
   *   | S.push( 1/(x*=S.pop(S.pop()))?x:Z );
   /   | S.push( 1/(x/=S.pop(S.pop()))?x:Z );
   :   | S.push( x );
   .   | S.push( );print(x );
   (   | S.push( );while(x){(0 );
   )   | S.push( )}(0 );
\$\endgroup\$
0
2
\$\begingroup\$

C (gcc) for Linux x86_64, 675 643 621 613 597 432 404 399 bytes

printf();*z;*mmap();(*p)();*j(char*a){char*t=a,*n,c;for(p=0;read(0,&c,!p);t=!~c?n=j(t+9),z=mempcpy(t,L"\xf00f883Ƅ",5),*z=n-t-9,n:!c?p=*t++=233,z=t,*z=a-13-t,z+1:stpcpy(t,c-85?c-2?c-4?c-1?c-6?c-17?"PAPTYh%ld T_P^1\xc0QH\x83\xcc\bQA\xff\xd0\\AXX":"P":L"\xfef7995e":"[\xf7\xeb":"[)\xd8":"[\1\xd8":L"\xf0c70f50"))c-=41;return t;}main(){p=mmap(0,1<<20,6,34,0,0);p(strcpy(j(p),"j<X\xf\5"),0,0,0,printf);}

Try it online!

This is a JIT that directly translates Volatile instructions into x86_64 machine language and executes the code. If your machine doesn't have the rdrand instruction, you can replace L"\xf0c70f50" with "Pj*X" for a "less uniform PRNG". To port to something other than Linux, replace the syscalls in the printf() and exit() blobs and adjust parameters to mmap().

EDIT: This version calls printf() instead of implementing a subset from scratch.

EDIT2: Supported integers are now 32 bits instead of 64.

Slightly less golfed...

printf();*z;*mmap();(*p)();
// recursive function translates Volatile commands to x86_64 instructions
*j(char*a){
  char*t=a,*n,c;
  for(p=0;read(0,&c,!p);)
    c-=41,
    t=c=='('+41?
      // cmp eax,0
      // je n-t-9
      n=j(t+9),
      z=mempcpy(t,"\x83\xf8\x00\x0f\x84",5),
      *z=n-t-9,
      n
    :
      c==')'+41?
        // jmp a-13-t
        p=*t++=233,
        z=t,
        *z=a-13-t,
        z+1
      :
        stpcpy(t,c-'~'+41?
                   c-'+'+41?
                     c-'-'+41?
                       c-'*'+41?
                         c-'/'+41?
                           c-':'+41?
                             // ; This calls printf("%ld ",%rax)
                             // push rax
                             // push r8
                             // push rsp
                             // pop  rcx
                             // push 0x20646c25
                             // push rsp
                             // pop  rdi
                             // push rax
                             // pop  rsi
                             // xor  eax, eax
                             // push rcx
                             // or   rsp, 8
                             // push rcx
                             // call r8
                             // pop  rsp
                             // pop  r8
                             // pop  rax
                             "\x50\x41\x50\x54\x59\x68\x25\x6c\x64\x20\x54\x5f\x50\x5e\x31\xc0\x51\x48\x83\xcc\x08\x51\x41\xff\xd0\x5c\x41\x58\x58"
                           :
                             // push rax
                             "\x50"
                         :
                           // pop rsi
                           // cdq  
                           // idiv esi
                           "\x5e\x99\xf7\xfe"
                       :
                         // pop rbx
                         // imul ebx
                         "\x5b\xf7\xeb"
                     :
                       // pop rbx
                       // sub eax, ebx
                       "\x5b\x29\xd8"
                   :
                     // pop rbx
                     // add eax, ebx
                     "\x5b\x01\xd8"
                 :
                   // push rax
                   // rdrand eax
                   "\x50\x0f\xc7\xf0");
  return t;
}
main(){
  p=mmap(0,1<<20,6,34,0,0);
  p(strcpy(j(p),"\x6a\x3c\x58\x0f\x05"),0,0,0,printf);
}
\$\endgroup\$
2
\$\begingroup\$

Java 8, 420 418 402 373 359 357 341 339 bytes

import java.util.*;s->f(s,new Stack());void f(String s,Stack<Integer>S){for(int i=0,c,t,u;i<s.length();){if((c=s.charAt(i++))<42&&S.peek()!=0)f(s.substring(40/c*i),S);if(c==46)System.out.println(S.peek());if(c>57)S.add(c>99?new Random().nextInt():S.peek());if(c<44|c==45|c==47){t=S.pop();u=S.pop();S.add(c<43?u*t:c<45?u+t:c<46?u-t:u/t);}}}

-2 bytes thanks to @Grimy.
-18 bytes thanks to @ceilingcat.

Try it online.

Explanation:

import java.util.*;       // Required import for 2x Stack and Random

s->                       // Method with String parameter and no return-type
  f(s,new Stack())        //  Call the recursive function, with a new Stack

// Separated recursive method with String and Stack parameters
void f(String s,Stack<Integer>S){
  int i=0,                //  Index integer
      c,                  //  Temp integer used for the current character
      t,u;                //  Temp integers used for the peeked/popped top of the stack
  for(;i<s.length();){    //  Loop `i` in the range [0, String-length):
    if((c=s.charAt(i      //   Set `c` to the current character
                    ++))  //   And increase index `i` by 1 right after
        <42               //   If the character is either '(' or ')',
        &&S.peek()!=0)    //   and the top of the stack is not 0:
         f(s.substring(   //    Take the substring, either removing everything before and
            40/c*i),      //    including the "(", or keeping the string as is for ")"
           S);            //    And do a recursive call with this String
    if(c==46)             //    If the character is '.'
      System.out.println( //     Print with trailing newline:
       S.peek());         //      The peeked top of the stack
    if(c>57)              //    If the character is ':' or '~':
      S.add(c>99?         //     If the character is '~':
        new Random().nextInt()
                          //      Add a random [0,2147483647) integer to the stack
       :                  //     Else (the character is ':')
        S.peek());        //      Add the peeked top to the stack
    if(c<44|c==45|c==47)  //    If the character is '*', '+', '-', or '/':
      t=S.pop();u=S.pop();//    Pop and set the top two values to `t` and `u`
      S.add(c<43?         //     If the character is '*':
        u*t               //      Add the product of the two values to the stack
       :c<44?             //     Else-if the character is '+':
        u+t               //      Add the sum of the two values to the stack
       :c<46?             //     Else-if the character is '-':
        u-t               //      Subtract the top two values, and add it to the stack
       :                  //     Else (the character is '/'):
        u/t;}}}           //      Divide the top two values, and add it to the stack
\$\endgroup\$
5
  • 1
    \$\begingroup\$ new Random().nextInt() is 2 shorter than (int)(Math.random()*1e5). \$\endgroup\$
    – Grimmy
    Sep 11, 2019 at 11:20
  • \$\begingroup\$ @Grimy Ah, forgot I had that java.util.* import for the Stack. Thanks! :) \$\endgroup\$ Sep 11, 2019 at 11:30
  • \$\begingroup\$ @ceilingcat Thanks for -16! \$\endgroup\$ Sep 17, 2019 at 6:23
  • \$\begingroup\$ This probably goes against one or more rules of decorum but if you replace new Random().nextInt() with 5 all testcases still pass. \$\endgroup\$
    – ceilingcat
    Sep 19, 2019 at 22:58
  • \$\begingroup\$ @ceilingcat Yeah, but 5 isn't exactly random ;) Relevant xkcd \$\endgroup\$ Sep 20, 2019 at 6:17
2
\$\begingroup\$

Python 2, 460 454 428 417 413 412 406 bytes

from random import*
a=raw_input();i=v=0;s=[];o='p(s.pop()%ss.pop())';l=len;p=s.append
def L(u,c,N,r):
 u=u[::r];f=0
 for i in range(l(u)):
    if c==u[i]:f+=1
    if f==N:return l(u)-i+l(u)*(r>0)
while i<l(a):c=a[i];exec['v+=1;'+'i=L(a,")",v,1)'*(c=='('and not s[-1]),'i=L(a,"(",v,-1)-1'if')'==c and s[-1]else'v-=1','p(randint(0,4e4))','p(s[-1])','print(int(s[-1]))',o%c,o%c,o%c,o%c,'']['()~:.+-*/'.find(c)];i+=1

Try it online!
Try it ungolfed!

-19 bytes thanks to emanresu
-26 bytes thanks to implicit loop errors
-2 bytes because -1 wraps to the end of the array
-1 byte by moving operators into main array
-6 bytes by fitting everything into the exec array

Transpiles to Python code.

Explanation

from random import*

We need this for access to randint, and of course importing everything is golfier than just importing randint

a=raw_input();i=v=0;s=[];o='p(s.pop()%ss.pop())';l=len;p=s.append

This defines a few initial variables.
a is the source code, read from STDIN
i is the instruction pointer, ie the index of the current character in source
v is used to track nested loops
s is the stack
o is a string template used for the four operators, they're all the same logic just with a different operator, and that operator is conveniently also the instruction that triggers it, so we can just interpolate the char into this string
l and p are just short aliases to save bytes on repeated functions
;l=len costs 6 bytes, but saves 2 on each call to len, so as long as we use len more than 3 times in the program, we save bytes.
;p=s.append costs 11 bytes, but saves 7 on each push to the stack, meaning it's worth it even if we only push twice.

def L(u,c,N,r):
 u=u[::r];f=0
 for i in range(l(u)):
    if c==u[i]:f+=1
    if f==N:return l(u)-i+l(u)*(r>0)

This function is used to find the opposite end of a loop while considering nested loops. u is the source code, c is the char to find, N is the nest depth, and r is the direction. Returns the index of the first char after the found character.

If for any reason this function can't find a matching end of the loop, it returns None, which then throws an error on the next iteration of the main while loop.

while i<l(a):c=a[i];exec['v+=1;'+'i=L(a,")",v,1)'*(c=='('and not s[-1]),'i=L(a,"(",v,-1)-1'if')'==c and s[-1]else'v-=1','p(randint(0,4e4))','p(s[-1])','print(int(s[-1]))',o%c,o%c,o%c,o%c,'']['()~:.+-*/'.find(c)];i+=1

This is the main parsing loop

while i<l(a):

Keep looping while the instruction pointer has not reached the end of the code

 c=a[i]

This is setup for each individual character.
c is the current character, ie the character at index i in the source code

 exec['v+=1;'+'i=L(a,")",v,1)'*(c=='('and not s[-1]),'i=L(a,"(",v,-1)-1'if')'==c and s[-1]else'v-=1','p(randint(0,4e4))','p(s[-1])','print(int(s[-1]))',o%c,o%c,o%c,o%c,'']['()~:.+-*/'.find(c)]

Finally, we get the index of c in the string '()~:.+-*/'. ie 0 for (, 1 for ), 2 for ~, 3 for :, 4 for ., 5-8 for an operator, and -1 for anything else, and then use that to index into this array with to get the relevant code to execute. As indexing with -1 in Python wraps to the end of the array, -1 will return '' here.

'v+=1;'+'i=L(a,")",v,1)'*(c=='('and not s[-1])

The transpiled code for ( increments v and, if the top of the stack is falsey, moves the instruction pointer to the end of the loop. We have to check that c=='(' so that s[-1] is not being evaluated on every instruction, causing an error when the program first starts and the stack is empty.

'i=L(a,"(",v,-1)-1'if')'==c and s[-1]else'v-=1'

The transpiled code for ) jumps to the beginning of the loop if the top of the stack is truthy, or otherwise decrements v. Again we have to check that ')'==c first to prevent s[-1] incorrectly evaluating on an empty stack.

p(randint(0,4e4))

The transpiled code for ~ pushes a random value between 0 and 40,000 (inclusive). 4e4 was the smallest 3-byte expression I found that was greater than or equal to the required max of 32,768, because 2**15 is still 5 bytes. Unfortunately I don't think it's possible to create a number large enough in fewer bytes.

p(s[-1])

The transpiled code for : simply pushes the top of the stack to the stack.

print(int(s[-1]))

The transpiled code for . prints the top of the stack with a trailing newline. Unfortunately we have to cast to an int costing 5 bytes as otherwise every value that's touched division will be printed with a trailing .0

o%c

The transpiled code for operators is the operator template o interpolated with the operator. ie:

p(s.pop()Xs.pop())

Where X is whatever character (one of +-*/) we're currently on.

We then exec the result of this indexing, running the transpiled code for the current instruction.

;i+=1

Last but not least, we increment i and repeat the loop.

\$\endgroup\$
4
  • \$\begingroup\$ If your PRNG has a very low variance (say, return 2 all the time) you may be able to omit the import. \$\endgroup\$
    – ceilingcat
    Nov 5, 2021 at 23:29
  • \$\begingroup\$ @ceilingcat I'm not sure what you mean, the random module is the only method I'm aware of to generate reasonably uniformly random numbers in python \$\endgroup\$
    – Mayube
    Nov 5, 2021 at 23:35
  • \$\begingroup\$ A "random number generator" that only returns 2 should pass all test cases. \$\endgroup\$
    – ceilingcat
    Nov 5, 2021 at 23:46
  • 1
    \$\begingroup\$ @ceilingcat That is not random \$\endgroup\$
    – Mayube
    Nov 5, 2021 at 23:47
2
\$\begingroup\$

PowerShell Core, 176 bytes

for($k=0;$c=$args[$k++]){switch -r($c){~{$t=,((get-random)%32768)+$t}'[-+*/]'{$a,$b,$t=$t
$t=,("$a$c $b"|iex)+$t}:{$t=,$t[0]+$t}'\.'{$t[0]}'\('{$l=$k-1}'\)'{if($t[0]){$k=$l}}}}

Try it online!

This is a special version that stops after a few iterations to validate the loop's behaviour: Try it online!

I saved about 50 bytes by faking a stack using arrays concatenation and deconstruction:

  • $a,$t=$t to pop values
  • $t=,$a+$t to push values
\$\endgroup\$
1
\$\begingroup\$

Kotlin, 412 bytes

Unfortunately I lost to Java, but I didn't want to import java.util.Stack (and I'm not sure it would close the gap anyway.)

{p->var i=0
var s=List(0){0}
var c=List(0){0}
while(i<p.length){when(val o=p[i]){'~'->s+=(0..32768).random()
in "+-*/"->{val(a,b)=s.takeLast(2)
s=s.dropLast(2)+when(o){'+'->a+b
'-'->a-b
'*'->a*b
'/'->a/b
else->0}}
':'->s+=s.last()
'.'->println(s.last())
'('->{if(s.last()!=0)c+=i else{var z=0
do{if(p[i]=='(')z++else if(p[i]==')')z--
i++}while(z>0)
i--}}
')'->if(s.last()!=0)i=c.last()else c=c.dropLast(1)}
i++}}

Ungolfed

{ p ->                  // open lambda: p is the code string
    var i = 0           // program counter
    var s = List(0){0}  // data stack
    var c = List(0){0}  // jump stack

    // main loop
    while(i<p.length) {
        // match on the current character
        when(val o = p[i]) {
            // add random number to end of stack
            '~' -> s += (0..32768).random()
            // if a math op...
            in "+-*/" -> {
                // pick top stack items
                val (a, b) = s.takeLast(2)
                // pop two items and then push based on op
                s = s.dropLast(2) + when(o) {
                    '+' -> a+b
                    '-' -> a-b
                    '*' -> a*b
                    '/' -> a/b
                    else -> 0  // else is required here
                }
            }
            // duplicate top stack item
            ':' -> s += s.last()
            // print top stack item
            '.' -> println(s.last())
            // open loop
            '(' -> {
                if(s.last()!=0)
                    // push to jump stack if top of data stack is nonzero
                    c+=i
                else {
                    // skip ahead
                    var z=0
                    do {
                        // seek to matching brace
                        if(p[i]=='(') z++ else if(p[i]==')') z--
                        i++
                    } while(z>0)
                    // ensure program counter doesn't go too far
                    i--
                }
            }
            // close loop
            ')' -> if(s.last()!=0) i=c.last() else c=c.dropLast(1)
        }
        // next character
        i++
    }
}

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Go, 660 bytes, fixed seed

import."math/rand"
func f(C string){for i,S,P:=0,[]uint{},[]int{};i<len(C);i++{if c:=C[i];c=='~'{
S=append(S,uint(Int()))}else if c=='+'{b:=S[len(S)-1];S=S[:len(S)-1];a:=S[len(S)-1];S=S[:len(S)-1];S=append(S,a+b)}else if c=='-'{
b:=S[len(S)-1]
S=S[:len(S)-1]
a:=S[len(S)-1]
S=S[:len(S)-1]
S=append(S,a-b)}else if c=='*'{b:=S[len(S)-1]
S=S[:len(S)-1]
a:=S[len(S)-1]
S=S[:len(S)-1]
S=append(S,a*b)}else if c=='/'{b:=S[len(S)-1]
S=S[:len(S)-1]
a:=S[len(S)-1]
S=S[:len(S)-1]
S=append(S,a/b)}else if c==':'{S=append(S,S[len(S)-1])}else if c=='.'{println(S[len(S)-1])}else if c=='('{P=append(P,i)}else if c==')'{if S[len(S)-1]<1{i=P[len(P)-1]}else{P=P[:len(P)-1]}}}}

Attempt This Online!

confirmed go worse than java

Most of this is handling the pops. The title has "fixed seed" because in Go, the random number generator starts with the same fixed seed, every time. You need to seed it with some changing value for it to be truly random (+29 bytes):

Go, 689 bytes, truly random

import(."math/rand";."time")
func f(C string){Seed(Now().Unix())
for i,S,P:=0,[]uint{},[]int{};i<len(C);i++{if c:=C[i];c=='~'{
S=append(S,uint(Int()))}else if c=='+'{b:=S[len(S)-1];S=S[:len(S)-1];a:=S[len(S)-1];S=S[:len(S)-1];S=append(S,a+b)}else if c=='-'{
b:=S[len(S)-1]
S=S[:len(S)-1]
a:=S[len(S)-1]
S=S[:len(S)-1]
S=append(S,a-b)}else if c=='*'{b:=S[len(S)-1]
S=S[:len(S)-1]
a:=S[len(S)-1]
S=S[:len(S)-1]
S=append(S,a*b)}else if c=='/'{b:=S[len(S)-1]
S=S[:len(S)-1]
a:=S[len(S)-1]
S=S[:len(S)-1]
S=append(S,a/b)}else if c==':'{S=append(S,S[len(S)-1])}else if c=='.'{println(S[len(S)-1])}else if c=='('{P=append(P,i)}else if c==')'{if S[len(S)-1]<1{i=P[len(P)-1]}else{P=P[:len(P)-1]}}}}

Attempt This Online!

\$\endgroup\$
1
\$\begingroup\$

Vyxal D, 215 bitsv1, 26.875 bytes

`~.(/``kεʀ℅`\…‛{:|`:0=ß•/`WḣĿ∑Ė

Try it Online!

Translates volatile to its vyxal equivalent (which is almost 1 to 1, given that volatile is a subset of Keg which is a subset of Vyxal).

\$\endgroup\$

Your Answer

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

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