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


Note that these programs may randomly fail





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


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


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> 

  • 2
    \$\begingroup\$ Is division supposed to round toward 0? \$\endgroup\$ – Grimmy Sep 10 '19 at 13:15
  • \$\begingroup\$ Does ~ push an integer, or any number? \$\endgroup\$ – mbomb007 Sep 10 '19 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 '19 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\$ – Kevin Cruijssen Sep 10 '19 at 14:56
  • 2
    \$\begingroup\$ Suggestion to add test case: ~:-.(~:/+.) \$\endgroup\$ – Night2 Sep 11 '19 at 10:32

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

  • 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\$ – Kevin Cruijssen Sep 10 '19 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 '19 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\$ – Kevin Cruijssen Sep 10 '19 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 '19 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\$ – Kevin Cruijssen Sep 10 '19 at 15:24

Julia 1.0, 334 bytes

for c in q
!')' ? (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

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!


Runic Enchantments, 266 264 bytes

U/lA`R耀`S͗/?7  :S͗/?+f1+S͗/?3  -S͗/?3     $ '$:S͗/?7  *S͗/
~/?)0l{͗/?8 {͗l}͗/U

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.


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.

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!


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.


Try the 1st program online!

Try the 2nd program online!

Try the 3rd program online!


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 );

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

// recursive function translates Volatile commands to x86_64 instructions
      // cmp eax,0
      // je n-t-9
        // jmp a-13-t
                             // ; 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
                             // push rax
                           // pop rsi
                           // cdq  
                           // idiv esi
                         // pop rbx
                         // imul ebx
                       // pop rbx
                       // sub eax, ebx
                     // pop rbx
                     // add eax, ebx
                   // push rax
                   // rdrand eax
  return t;

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.


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
  • 1
    \$\begingroup\$ new Random().nextInt() is 2 shorter than (int)(Math.random()*1e5). \$\endgroup\$ – Grimmy Sep 11 '19 at 11:20
  • \$\begingroup\$ @Grimy Ah, forgot I had that java.util.* import for the Stack. Thanks! :) \$\endgroup\$ – Kevin Cruijssen Sep 11 '19 at 11:30
  • \$\begingroup\$ @ceilingcat Thanks for -16! \$\endgroup\$ – Kevin Cruijssen Sep 17 '19 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 '19 at 22:58
  • \$\begingroup\$ @ceilingcat Yeah, but 5 isn't exactly random ;) Relevant xkcd \$\endgroup\$ – Kevin Cruijssen Sep 20 '19 at 6:17

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)
'('->{if(s.last()!=0)c+=i else{var z=0
do{if(p[i]=='(')z++else if(p[i]==')')z--
')'->if(s.last()!=0)i=c.last()else c=c.dropLast(1)}


{ 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
            '(' -> {
                    // push to jump stack if top of data stack is nonzero
                else {
                    // skip ahead
                    var z=0
                    do {
                        // seek to matching brace
                        if(p[i]=='(') z++ else if(p[i]==')') z--
                    } while(z>0)
                    // ensure program counter doesn't go too far
            // close loop
            ')' -> if(s.last()!=0) i=c.last() else c=c.dropLast(1)
        // next character

Try it online!


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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