Interpret StackyMath!

14

Time for you to implement my new stack based language! It's called StackyMath. This will be a stack based language with 8 operations on the stack and ways to add numbers to the stack.

List of operations:

  • /: Division. Performed on the top 2 numbers of the stack. Pushes the result back on the stack.
  • *: Multiplication. Performed on the top 2 numbers of the stack. Pushes the result back on the stack
  • -: Subtraction. Performed on the top 2 numbers of the stack. Pushes the result back on the stack
  • +: Addition. Performed on the top 2 numbers of the stack. Pushes the result back on the stack
  • ^: Exponentiation. Performed on the top 2 numbers of the stack. Pushes the result back on the stack
  • %: Modulo. Performed on the top 2 numbers of the stack. Pushes the result back on the stack
  • !: Factorial. Performed on the top number on the stack. Pushes the result back on the stack
  • D: Duplicate the top number on the stack

Operations defined in pseudo code:

  • /: push(pop divided by pop)
  • *: push(pop times pop)
  • -: push(pop minus pop)
  • +: push(pop plus pop)
  • ^: push(pop to the pop)
  • %: push(pop mod pop)
  • !: push(factorial pop)
  • D: t = pop; push(t); push(t)

How to push numbers to the stack:

Adding numbers to the stack is easy, just put the raw number in your program where you need it. If you need to put multiple numbers on the stack you can separate them with a comma (,). Your program will not need to process - numbers in the input, If the user wants one they should push the number they want negated, zero, and -. Numbers in the input of the program are also constrained to positive integers.

Input:

Your program should take the input on the command line, or from std in. Input will only consist of numbers (no scientific notation or decimals) delimited by , as needed, and the operations defined above.

Output:

Your program should print the number on the top of the stack.

Error cases:

  • If the program tries to over-pop the stack, you should print StackUnderflowException!!! .
  • If you have division by zero, print DivisionByZeroException!!!
  • If a number that exceeds 64-bits, either while executing the program or processing a number in the input, print NumberOverflowException!!!
  • If somehow you get a negative number on the top of the stack and you need to do a factorial, print NegativeFactorialException!!!
  • If you have a floating point number on the tops of the stack and the next operation is factorial, print FloatingFactorialException!!!
  • If no numbers are on the stack when the program exits (i.e. the program was empty) print EmptyProgram!!!

Notes:

  • All error output to should got yo std err or closest equivalent.
  • All numbers are constrained to 64-bit floating point.

Example programs:

50,47*                 -> 2350
50,47/                 -> 0.94
100,8!                 -> 40320  
100D*                  -> 10000
!                      -> StackUnderflowException!!!
5,2/!                  -> FloatingFactorialException!!!  
4,3!2*/                -> 3 
654,489,48,43/5*7D+-*% -> 77.68749999999909
                       -> EmptyProgram!!!

(I can add more if needed)

J Atkin

Posted 2015-12-02T20:24:39.230

Reputation: 4 846

3

If it weren't for the Error cases, Vitsy could do this naturally (except converting ! to F).

– Addison Crump – 2015-12-02T20:36:46.620

I figured, that's partly why I included them. – J Atkin – 2015-12-02T20:37:34.903

3

Yours is somewhat broader in scope, though it may be arguable that its a duplicate: http://codegolf.stackexchange.com/questions/221/reverse-polish-notation

– Digital Trauma – 2015-12-02T22:29:58.463

Wow, I forgot about that one. But I don't think they are dupes since you have to process errors and more operators are defined in mine. – J Atkin – 2015-12-02T23:07:27.123

654,489,48,43/5*7D+-*% should return 77.6875. (43/48*5-(7+7) should be (7+7)-43/48*5) – user81655 – 2015-12-03T01:32:44.300

Aww crap. Quite right, I messed it up. Fixing now. – J Atkin – 2015-12-03T01:43:49.740

Is it fine if we throw an exception with the correct text? Example: throw 'EmptyProgram!!!'; – usandfriends – 2015-12-03T02:38:32.777

Shoulda thought of that. No, you can't. This may seem arbitrary but kinda the idea is to create your own language with its own exceptions, you don't want to see the interpreter throwing exceptions. – J Atkin – 2015-12-03T02:59:33.570

Does that mean my answer is invalid? I designed it to be run without needing a console, so the error message is displayed, but if you looked at the console you would see that it threw an exception also. – user81655 – 2015-12-03T03:20:20.573

No, I think its fine. Just as long as the user can't see it by default. – J Atkin – 2015-12-03T04:30:16.827

In the requirement you only mentioned that in case of error a message has to be printed to STDERR. However your answer also exits in case of error. Is exiting mandatory or optional?

– manatwork – 2015-12-03T11:04:55.800

Optional. I did it because exiting was the shortest way to stop the program. I didn't want to continue processing the program if it has errors – J Atkin – 2015-12-03T15:24:41.713

Answers

4

Ruby, 412 410 404 392 380 377 characters

def e m,x='Exception';warn m+x+?!*3;exit;end
def o;e'StackUnderflow'if$*==[];$*.pop;end
u=->n{e'DivisionByZero'if n.infinite?;e'NumberOverflow'if n>2**64;$*<<n}
f=->n{e'NegativeFactorial'if n<0;e'FloatingFactorial'if n.to_i<n;n<2?1:f[n-1]*n}
gets.gsub(/(\d+)|([+*\/%^-])|(!)|D/){$1?u[$1.to_f]:$2?u[eval"o#{$2>?A?:**:$2}o"]:$3?u[f[o]]:u[x=o]+u[x]}
e'EmptyProgram',''if$*==[]
p o

This is regular precision version using Float. The result precision is as in the sample code, but numeric overflow detection is not exact.

Sample run:

bash-4.3$ ruby StackyMath.rb <<< '654,489,48,43/5*7D+-*%'
77.68749999999909

Ruby, 378 377 characters

def e m,x='Exception';warn m+x+?!*3;exit;end
def o;e'StackUnderflow'if$*==[];$*.pop;end
u=->n{e'NumberOverflow'if n>2**64;$*<<n}
f=->n{e'NegativeFactorial'if n<0;e'FloatingFactorial'if n.to_i<n;n<2?1:f[n-1]*n}
gets.gsub(/(\d+)|([+*\/%^-])|(!)|D/){$1?u[Rational$1]:$2?u[eval"o#{$2>?A?:**:$2}o"]:$3?u[f[o]]:u[x=o]+u[x]}rescue e'DivisionByZero'
e'EmptyProgram',''if$*==[]
p o.to_f

This is high precision version using Rational. The result precision is not always the same as in the sample code, but numeric overflow detection is exact.

Sample run:

bash-4.3$ ruby StackyMath-hi.rb <<< '654,489,48,43/5*7D+-*%'
77.6875

manatwork

Posted 2015-12-02T20:24:39.230

Reputation: 17 865

3

JavaScript (ES6), 430 bytes

422 bytes with ES7 by changing Math.pow(2,2) to 2**2

e=m=>{throw alert(m)};u=prompt();u?alert(eval('u.match(/\\d+|[^,]/g).map(o=>s.push(t=o=="/"?(b=p(a=2))?a/b:e`DivisionByZero43*"?2*23-"?2-23+"?2+23%"?2%23^"?Math.pow(2,2)3D"?s.push(r=2)&&r3!"?eval("for(r=i=2;i<0?e`Negative54:i%1?e`Floating54:--i;)r*=i;r"):+o)&&t==Infinity&&e`NumberOverflow4,s=[],p=_=>s.length?s.pop():e`StackUnderflow4);t'.replace(/[2-5]/g,x=>[,,'p()',':o=="','Exception!!!`','Factorial'][x]))):e`EmptyProgram!!!`

Explanation

Uses eval to replace certain common phrases. Ungolfed and without the eval it looks like this:

e=m=>{throw alert(m)};                           // e = throw error, alert displays
                                                 //     message, throw stops execution
u=prompt();                                      // u = received input
u?alert(                                         // display the result
  u.match(/\d+|[^,]/g)                           // get array of numbers and operators
    .map(o=>                                     // iterate over operators
      s.push(t=                                  // t = last pushed value

        // Execute operator
        o=="/"?(b=p(a=p()))?a/b:                 // make sure argument B is not 0
          e`DivisionByZeroException!!!`:
        o=="*"?p()*p():
        o=="-"?p()-p():
        o=="+"?p()+p():
        o=="%"?p()%p():
        o=="^"?Math.pow(p(),p()):
        o=="D"?s.push(r=p())&&r:
        o=="!"?eval("                            // eval to enable for loop in ternary
          for(                                   // no factorial in JS so do this manually
            r=i=p();
            i<0?e`NegativeFactorialException!!!` // check for errors
              :i%1?
                e`FloatingFactorialException!!!`
                :--i;
          )
            r*=i;
          r"):                                   // return r
        +o                                       // if not an operator cast as a number
      )&&t==Infinity&&                           // JS turns anything over 64 bit float
        e`NumberOverflowException!!!`,           //     max value into Infinity
      s=[],                                      // s = stack array
      p=_=>s.length?s.pop():                     // p = check stack then pop
        e`StackUnderflowException!!!`
    )&&t                                         // return top stack element
  ):e`EmptyProgram!!!`                           // error if input length is 0

user81655

Posted 2015-12-02T20:24:39.230

Reputation: 10 181

If you want to upgrade to ES7, you could use the ES7 exponentiation operator to replace Math.pow(p(),p()) with p()**p().

– usandfriends – 2015-12-03T04:54:27.060

1@usandfriends I was thinking about it, but it meant that it would not work in my browser so I left it out. :P I'll add a note saying that. – user81655 – 2015-12-03T05:07:06.953

1

Groovy, 718 bytes. Fore!

May as well post my impl golfed. Meet my big wall of code:

g='Exception!!!'
a={System.err.print(it);System.exit(1)}
b=new Stack()
c={b.push(it)}
v=2d**64d
d={b.pop()}
e={if(b.size()<it)a('StackUnderflow'+g)}
f={a('NumberOverflow'+g)}
h={e(2)
c(Eval.xy(d(),d(),"x$it y"))
if(b.peek()>v)f()}
k={i->if(i<0)a('NegativeFactorial'+g)
if(Math.abs(i-(i as long))>1E-6)a('FloatingFactorial'+g)
(2..i).inject{x,y->(v/x<y)?f():x*y}}
l=['/':{e(2)
m=d()
n=d()
if(n==0)a('DivisionByZero'+g)
c(m/n)},'!':{e(1)
c(k(d()))},'D':{e(1)
c(b.peek())}]
System.in.newReader().readLine().findAll(~/\d+|[^,]/).each{x->if(x.matches(/\d+/))try{c(x as double)}catch(Exception e){f()}
else if("+-*%^".contains(x))h(x.replace('^','**'))
else l[x]()}
if(b){o=d()
if(Double.isInfinite(o))f()
println o}else a('EmptyProgram!!!')

Ungolfed:

error = {System.err.print(it);System.exit(1)}

stack = new Stack()
maxVal = 2d**64d

push = {stack.push(it)}
pop = {stack.pop()}

checkAtLeast = {if (stack.size() < it) error('StackUnderflow'+exception)}
numberOverflow = {error('NumberOverflow'+exception)}

exception = 'Exception!!!'

def dArgOp(i) {
    checkAtLeast(2)
    push(Eval.xy(pop(), pop(), "x$i y"))
    if(stack.peek() > maxVal) numberOverflow
}

factorial = {i->
    if (i < 0)
        error('NegativeFactorial'+exception)
    if (Math.abs(i - (i as long)) > 1E-6)
        error('FloatingFactorial'+exception)
    (2..i).inject {acc, it ->
        (maxVal/acc < it)?numberOverflow():acc*it
    }
}

ops = [
'/' : {
    checkAtLeast(2)
    first = pop()
    second = pop()
    if (second == 0)
        error('DivisionByZero'+exception)
    push(first / second)
},
'!' : {
    checkAtLeast(1)
    push(factorial(pop()))
},
'D' : {
    checkAtLeast(1)
    push(stack.peek())
}]

input = System.in.newReader().readLine()
tokens = input.findAll(~/\d+|[^,]/)

tokens.each {
    print "current token: $it  \t stack before eval: $stack "
    if (it.matches(/\d+/))
        try {
            push(it as double)
        } catch (Exception e) {
            numberOverflow()
        }

    else if ("+-*%^".contains(it))
        dArgOp(it.replace('^','**'))
    else
        ops[it]()
    println "result: ${stack.peek()}"
}

if (stack) {
    top = pop()
    if (Double.isInfinite(top))
        numberOverflow()
    println top
} else
    error('EmptyProgram!!!')

Edit 1: save ~15 bytes thanks to @Doorknob
Edit 2: drop ~130 bytes with a few more tricks

J Atkin

Posted 2015-12-02T20:24:39.230

Reputation: 4 846

I don't know Groovy, but you seem to have lots of unnecessary whitespace. For example, around operators, after for/if, etc – Doorknob – 2015-12-02T21:13:36.647

Whoops, just noticed many more places to remove whitespace. Thanks for the tip. – J Atkin – 2015-12-02T21:22:11.677

You can use System.in.text instead of System.in.newReader().readLine(). – a spaghetto – 2015-12-03T00:47:45.287

Not really. .text is greedy and as long as data in in the reader, it won't return. – J Atkin – 2015-12-03T00:52:59.890

Right, but this is code-golf. It's not a big deal if people have to type Control-D after their input. – a spaghetto – 2015-12-03T01:50:36.253

Right, forgot about Ctrl-D. I almost never use the command line. It doesn't matter since I will remove this shortly in favor of the python answer I'm working on. – J Atkin – 2015-12-03T03:01:29.807

@quartata as it turns out my skills + python + this challenge is more than 900 bytes, so I plan to keep this one. Oh and for some reason Ctrl-D quits the process, not end the std in. Do you use OSX or lunix? – J Atkin – 2015-12-03T23:31:24.333

I use Linux. I'm not sure why Ctrl-D would quit the process (it just means EOF). Try giving your input like this: groovy script <<< INPUT – a spaghetto – 2015-12-04T00:24:35.963

Nope, still doesn't work. Perhaps it's a bash feature. – J Atkin – 2015-12-04T00:41:08.890

Does it require a specific version of Groovy? The 1.8.6 I use raises exception on factorial: “groovy.lang.MissingMethodException: No signature of method: groovy.lang.ObjectRange.inject()”. – manatwork – 2015-12-04T16:22:09.623

It looks like you would need 1.8.7 min (from the docs) but I'm not sure. I am using groovy 2.4.5 right now.

– J Atkin – 2015-12-04T16:27:19.457

Not getting NumberOverflowException when calculating 65,2^ is also a version difference? – manatwork – 2015-12-04T16:36:05.623

I'm not sure what is going on when I do that. It would seem that I'm using big decimal instead of double.... I'll see if I can fix that soon. – J Atkin – 2015-12-04T16:42:50.853

Quite odd. It looks like I'm using double (getClass().getSimpleName() is Double) but increasing the value has no effect. Is double replaced with BigDecimal in groovy? – J Atkin – 2015-12-04T16:51:27.830

@manatwork Weird, Double has Double.MAX_EXPONENT, set to 1023. So 2d**1024d is Infinity, but 2d**64d is just fine... – J Atkin – 2015-12-04T16:54:31.270

If you ask me, that 64 bit thing is the requirement's issue. ;) In a certain degree it should appear in most of the languages. (For my Ruby solution there would be a trick to use Rational instead, but then you get higher precision for the result too, not just the error checking. I mean, 654,489,48,43/5*7D+-*% would result 77.6875.) – manatwork – 2015-12-04T16:54:32.673

I'm not sure I can fix it now, It's a little late to change the question ;) I will go ahead and fix my answer though. – J Atkin – 2015-12-04T16:59:07.260

@manatwork That should fix it. Thanks for catching that problem. – J Atkin – 2015-12-04T17:08:23.627

Now it works like in Ruby: 64,2^1000+ passes and gets calculated, 64,2^10000+ raises the exception. I strongly believe there is no better solution given the machine's internal floating point representation. – manatwork – 2015-12-04T17:12:28.637

1

Candy, 298 348 392 bytes

Although Candy is stack based, I'm not sure that really helped...

&{|"EmptyProgram!!!\n"(;).}(=bYZay0=zx4k"!%*+,-/D^"(iYe{Z=x})aYb=z=ya=X{Y{cZ0=yza}b212202221(i=ZXe{y})a0=zcY0j{XjZ{|D1b#64R(=c2*)c>{b"NumberOverFlow"(;)i}}|i}aZ{(=)"Exception!!!\n"(;).}0=yz|A#48-Z#10*+=z1=y})c?(=).@0&<{1|b"StackUnderflow"(;)c0}.@1~ALe{A0<{b"Negative"(;)i|1bAR(=cA*)}|b"Floating"(;)i}Z{b"Factorial"(;)}.@2W%.@3*.@4+@5.@6W-.@7WD{/|b"DivisionByZero"(;)i}.@8~A.@9=xK=y=1bA_(cX*).

Formatted a bit reveals a bit of structure:

&{|"EmptyProgram!!!\n"(;).}
(=bYZay0=zx4k
  "!%*+,-/D^"
  (iYe{Z=x})
  aYb=z=ya=X
  {
    Y{cZ0=yza}b
    212202221(i=ZXe{y})
    a0=zcY0j
    {XjZ{|D1b#64R(=c2*)c>{b"NumberOverFlow"(;)i}}|i}
    aZ{(=)"Exception!!!\n"(;).}
    0=yz|A#48-Z#10*+=z1=y
  }
)c?(=).
@0&<{1|b"StackUnderflow"(;)c0}.
@1~ALe{A0<{b"Negative"(;)i|1bAR(=cA*)}|b"Floating"(;)i}Z{"Factorial"(;)}.
@2W%.@3*.@4+@5.@6W-.@7WD{/|"DivisionByZero"(;)i}.@8~A.@9=xK=y=1bA_(cX*).

The actual math occurs on the last two lines. It's driven there by a jump table on the third line.

Dale Johnson

Posted 2015-12-02T20:24:39.230

Reputation: 509

Dang, I see that I missed DivisionByZero, NegativeFactorial, and Overflow. I was just looking at the test cases! – Dale Johnson – 2015-12-04T12:14:28.923

Wow, this is cool. I just might need to look up candy. – J Atkin – 2015-12-04T14:11:20.437

I'm still working on how exactly to define overflow. – Dale Johnson – 2015-12-04T19:20:03.120

Actually I had the same problem in my answer. See the comments below my answer. – J Atkin – 2015-12-04T19:22:40.200

Fixed the Overflow now. I used the same approach as the Ruby, which is just to compare to 2^64 at the end of each operation. – Dale Johnson – 2015-12-04T20:08:54.773