[QUIZ] Bytecode Compiler (#100)

Aww man, now I'm gonna have to look into Haskell a bit more :slight_smile: I've been
putting it off for a while, but this is too cool to not have a play
with!

Because I'm currently Haskell-ignorant, however, I don't know how to run
your solution :frowning: . What interpreter (or compiler?) should be used? I
have Hugs installed, but that complains about a "Syntax error in input
(unexpected backslash (lambda))" at line 1.

···

On Wed, 2006-11-08 at 08:47 +0900, Justin Bailey wrote:

Some people started doing the Ruby quiz problems using Haskell, and
this was a perfect opportunity for me to learn some Haskell. So here's
my solution below, in Haskell. It's hard to test the byte code
interpretation but all the expression do evaluate to the correct
values.

If anyone has questions about the Haskell code, please let me know.
I'm just learning it and its really cool!

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

James Edward Gray II wrote:

···

On Nov 5, 2006, at 11:18 AM, Dennis Ranke wrote:

Here is my solution, a simple recursive descent parser. It's a bit more code than is strictly necessary because it is loosely modeled after a parser for a real language I have written recently.

Did you write that language in Ruby? I'm just curious.

I wrote the (fairly simple) compiler in Ruby and the bytecode interpreter in C++. The whole thing is used as a scripting language for a Nintendo DS game.

Here's my effort. I really enjoyed this quiz but spent way more than the 1 hour on it (and it's still not correct)!

I tokenized the expression with a regex and then used the Shunting Yard Algorithm to do the parsing. However, I fail some of tests -

1. Unary minus [-2+(2-2)]

2. Order of evaluation in this expression is right-->left as opposed to left-->right and gives wrong result [(1+3)/(2/2)*(10-8)]

3. The optional efficient storage test.

I'll try to fix these if I have more time.

Thanks

Bob

require "Interpreter.rb"

class Compiler

   @PRECEDENCE = {
     '(' => -1,
     ')' => -1,
     '+' => 0,
     '-' => 0,
     '*' => 1,
     '/' => 1,
     '**' => 3,
     '%' => 3
   }

   @BYTECODE = {
     'CONST' => 0x01,
     'LCONST' => 0x02,
     '+' => 0x0a,
     '-' => 0x0b,
     '*' => 0x0c,
     '**' => 0x0d,
     '/' => 0x0e,
     '%' => 0x0f,
     'SWAP' => 0xa0
   }

   def Compiler.compile(expression)
     te = self.tokenize(expression)
     be = self.parse(te)
     self.to_bytecode(be)
   end

   def Compiler.tokenize(expression)
     tokenized_expression = []

     expression.gsub!(/\s+/, "")
     expression.scan(/(\d+|\(|\)|\+|-|\*\*|\*|\/|\%)/) { |e| tokenized_expression.push(e) }
     tokenized_expression
   end

   def Compiler.parse(tokenized_expression)
     output_queue, operator_stack = [], []
     operator = nil

     tokenized_expression.each { |token|

       # If token is a number, place on output queue
       if (token[0] =~ /^\d+$/)
         output_queue.push(token[0].to_i)
       elsif (token[0] == '(')
         operator_stack.push(token[0])
       elsif (token[0] == ')')
         # Pop operators off stack and onto output queue until left
         # bracket encountered
         while (operator != '(')
           if ((operator = operator_stack.pop) != '(')
               output_queue.push(operator)
           end
         end
       else
         # If there are any operators, check precedence of current token
         # against last operator on queue. If the operator on queue is
         # more important, add it to the output before pushing the current
         # operator on
         if (operator_stack.any? && (@PRECEDENCE[token[0]] <= @PRECEDENCE[operator_stack.last]))
           output_queue.push(operator_stack.pop)
         end
         operator_stack.push(token[0])
       end
     }

     # Add the remaining operators to end of the output queue
     operator_stack.reverse_each { |operator|
       output_queue.push(operator)
     }

     output_queue
   end

   def Compiler.to_bytecode(bnf_expression)
     stack = []

     bnf_expression.delete("(")
     bnf_expression.each { |token|
       case token
         when Integer
            # If number is small enough, use smaller 2 byte storage

           if ((token >= -32768) && (token <= 32767))
             stack.push(@BYTECODE['CONST'])
             stack.push(token >> 8, token)
           else
             stack.push(@BYTECODE['LCONST'])
             stack.push(token >> 24, token >> 16, token >> 8, token)
           end
         else
           stack.push(@BYTECODE[token])
       end
     }
     stack
   end

end

require "TestByteCode.rb"

Aww man, now I'm gonna have to look into Haskell a bit more :slight_smile: I've been
putting it off for a while, but this is too cool to not have a play
with!

You won't be disappointed. Haskell is an amazingly cool language.

Because I'm currently Haskell-ignorant, however, I don't know how to run
your solution :frowning: . What interpreter (or compiler?) should be used? I
have Hugs installed, but that complains about a "Syntax error in input
(unexpected backslash (lambda))" at line 1.

It looks like its supposed to be embedded in LaTeX. Just remove the
first and last lines since the TeX directives look like lambdas to
Haskell.

\x -> x * x
  <==>
lamba {|x| x*x}

Also, Haskell is very picky about formatting. Your going to get a
bunch of errors because of line wraps.

···

On 11/9/06, Ross Bamford <rossrt@roscopeco.co.uk> wrote:

--
Lou.

Aww man, now I'm gonna have to look into Haskell a bit more :slight_smile: I've been
putting it off for a while, but this is too cool to not have a play
with!

Awesome!

Because I'm currently Haskell-ignorant, however, I don't know how to run
your solution :frowning: . What interpreter (or compiler?) should be used? I
have Hugs installed, but that complains about a "Syntax error in input
(unexpected backslash (lambda))" at line 1.

Like someone else pointed out, it's meant to be in a LateX document.
It's called "literate coding" and Haskell is the first place I
encountered it. The basic idea is you flip the normal source/comments
order and make comments the number 1 element in the document. Code is
then embedded in \begin{code} & \end{code} directives. (throwaway
comment: enabling ruby to run "literate code" would make a cool quiz).

Anyways, to run a literate haskell file in hugs, just save it with a
"lhs" extension. At least, that works for me in WinHugs.

To get a properly formatted version, its available on the web at:

http://www.haskell.org/haskellwiki/Haskell_Quiz/Bytecode_Compiler/Solution_Justin_Bailey

That version actually works (I'll be resubmitting in a minute). To run
all the tests there, type "interpret_tests" after you load the file.

Justin

p.s. One nice thing about literate haskell is you can copy that entire
page (i.e. don't worry about trying to grab only the code), paste it
into an lhs file, and run it. Works great on blogs too!

···

On 11/9/06, Ross Bamford <rossrt@roscopeco.co.uk> wrote:

Aww man, now I'm gonna have to look into Haskell a bit more :slight_smile: I've been
putting it off for a while, but this is too cool to not have a play
with!

You won't be disappointed. Haskell is an amazingly cool language.

It certainly looks it, too. I've glanced a few times, and put it on my list of things to do, but now I've got a reason to bump it up the list a bit :slight_smile:

Because I'm currently Haskell-ignorant, however, I don't know how to run
your solution :frowning: . What interpreter (or compiler?) should be used? I
have Hugs installed, but that complains about a "Syntax error in input
(unexpected backslash (lambda))" at line 1.

It looks like its supposed to be embedded in LaTeX. Just remove the
first and last lines since the TeX directives look like lambdas to
Haskell.

Ahh, gotcha. I did wonder, but being totally Haskell-ignorant I didn't want to just start deleting stuff :slight_smile:

\x -> x * x
  <==>
lamba {|x| x*x}

Also, Haskell is very picky about formatting. Your going to get a
bunch of errors because of line wraps.

Yeah, I think I'm finding that now. Never mind, at least I'll get the chance to play with Haskell a bit today as I try to get it going :slight_smile:

Cheers,

···

On Thu, 09 Nov 2006 12:00:38 -0000, Louis J Scoras <louis.j.scoras@gmail.com> wrote:

On 11/9/06, Ross Bamford <rossrt@roscopeco.co.uk> wrote:

--
Ross Bamford - rosco@roscopeco.remove.co.uk

Great, write it up for us so we can all learn how it works!

suggestion@rubyquiz.com

James Edward Gray II

···

On Nov 9, 2006, at 12:58 PM, Justin Bailey wrote:

(throwaway comment: enabling ruby to run "literate code" would make a cool quiz).