Programming Forums
User Name Password Register
 

RSS Feed
FORUM INDEX | TODAY'S POSTS | UNANSWERED THREADS | ADVANCED SEARCH

Reply
 
Thread Tools Display Modes
Old Jun 11th, 2007, 8:36 PM   #1
Jessehk
The Oblivious One
 
Jessehk's Avatar
 
Join Date: May 2005
Location: Ontario, Canada
Posts: 644
Rep Power: 4 Jessehk is on a distinguished road
[Ocaml] Interactive (althought currently limited) calculator

I did this in about 3 hours. It's an interactive calculator that can compute addition, subtraction, multiplication, and division. It currently only handles integers and does not recognise brackets or operator precedence.

Also, this is the first time I've written something like this, and I didn't use any resources in order to figure out how to do any of the parsing or calculation. As a result, I bet the code could be greatly improved using common idioms for this type of program.

It was a fun exercise and I think it's pretty neat. :p

(*
Copyright 2007 Jesse H-K

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*)

(*
 * An interactive calculator.
 * Only works with integers.
 * Includes division, multiplication, addition, and subtraction.
 * No operator precedence is currently implemented (and there are no brackets).
 *)


(* First, strip the input string of whitespace. *)
let strip_whitespace =
    let whitespace = Str.regexp "[ \n\t]*" in
    Str.global_replace whitespace ""
    
exception Invalid_token of string
exception Unexpected_token of string

(* Define all acceptable tokens in the expression. *)
type token =
    | Numb of int
    | Op of (int -> int -> int)
    | Begin (* A bit of a hack *)
    
(* Simply define the possible operations *)
let add = ( + ) 
let subtract = ( - )
let multiply = ( * )
let divide = ( / )
    
let int_of_char = function
    | '0' -> 0
    | '1' -> 1
    | '2' -> 2
    | '3' -> 3
    | '4' -> 4
    | '5' -> 5
    | '6' -> 6
    | '7' -> 7
    | '8' -> 8
    | '9' -> 9
    | ch -> failwith (Printf.sprintf "Can't convert char to int: \'%c\'" ch)
    
let token_of_char = function
    | '+' -> Op add
    | '-' -> Op subtract
    | '*' -> Op multiply
    | '/' -> Op divide
    | ch ->
        try
            Numb (int_of_char ch) 
        with _ -> raise (Invalid_token (Printf.sprintf "%c" ch))
        
let is_operand = function
    | Op _ -> true
    | _ -> false

let is_number = function
    | Numb _ -> true
    | _ -> false

let unexpected_operand ch = raise (Unexpected_token (Printf.sprintf "%c" ch))

(* Determines if a string begins with a number *)    
let is_number_string string =
    let number = Str.regexp "^[-+]?[0-9]+" in
    Str.string_match number string 0
    
(* Returns the string following a number in the string. *)
let string_after_number string =
    if not (is_number_string string) then failwith "Unable to convert string to number."
    else Str.string_after string (Str.match_end () )

(* Return a token list from a string. *)
let tokenize string =
    let raw = strip_whitespace string in
    
    (*
     A tail-recursive function.
       
     "result" is the accumulated list of tokens.
     "expr" is the expression remaining to tokenize.
     "prev" is the previous token parsed.
     *)
    let rec tr_tokenize result expr prev =
        (* On an empty remainder, return the result *)
        if expr = "" then result
        else (
            let char = expr.[0] in
            if (is_operand prev) && (is_operand (token_of_char char)) then unexpected_operand char
            else (
                (* If the first char of the expression is a digit, look for a number *)
                if is_number (token_of_char char) then
                    let rest = string_after_number expr in
                    let numb = Numb (int_of_string (Str.matched_string expr)) in
                    
                    (* Add the number to the token list, and tokenize the rest of the expression. *)
                    tr_tokenize (result @ [numb]) rest numb
                else (
                    (* It's not a digit, so it must be a operand. Error handling covered in token_of_char *)
                    let op = token_of_char char in
                    let rest = Str.string_after expr 1 in
                    
                    tr_tokenize (result @ [op]) rest op)))
    in
    
    tr_tokenize [] raw Begin

(* Given an Op, and two Numbs, return the result *)
let apply_operation op a b = match (op, a, b) with
    | (Op operand, Numb x, Numb y) -> operand x y
    | _ -> failwith "apply_operation"
    
exception Invalid_expression of string

(* Evaluate mathematical expressions. *)
let evaluate string =
    let token_list = tokenize string in
    
    let rec tr_evaluate result tlist = match tlist with
        | op :: b :: xs -> tr_evaluate (apply_operation op (Numb result) b) xs
        | [] -> result
        | _ -> raise (Invalid_expression string)
    in
    
    match token_list with
        (* A normal expression beginning with a number *)
        | (Numb n) :: xs -> tr_evaluate n xs
        
        (* Handles expressions like "-3 + 4". ie, expressions that begin with an operator *)
        | (Op op) :: (Numb a) :: xs -> tr_evaluate (op 0 a) xs
        
        (* Given an empty list, the answer is 0 *)
        | [] -> 0
        
        (* Anything else is an invalid expression *)
        | _ -> raise (Invalid_expression string)
        
let main_loop () = 
    while true do
        print_string ">> ";
        let input = read_line () in
        
        try
            print_int (evaluate input);
            print_newline ();
        with error -> print_endline (Printexc.to_string error);
   done
  
let _ = main_loop ()

compiling...
$ ocamlfind opt -package str -linkpkg parse.ml -o parse

running...
$ ./parse
>> 2 + 3
5
>> 10 + 30
40
>> -10 - 30
-40
>> -10 - 
Parse.Invalid_expression("-10 - ")
>> 10 +-
Parse.Unexpected_token("-")
>> 3 + a
Parse.Invalid_token("a")
>> 3 + 2
5
>> 45 * 100
4500
>> 45 * 100 + 2
4502
>> 100 / 3
33
>> 50 / 2
25
>> 50 / 2 / 3
8
>> 100 + 2 - 4 + 8 * 50 / 10
530
>> Fatal error: exception End_of_file
__________________
Dr. Zoidberg: [ecstatic] I'm going to a movie... with FRIENDS!
Jessehk is offline   Reply With Quote
Old Jun 11th, 2007, 10:25 PM   #2
ReggaetonKing
Sexy Programmer
 
ReggaetonKing's Avatar
 
Join Date: Nov 2005
Location: New Jersey
Posts: 891
Rep Power: 3 ReggaetonKing is on a distinguished road
Send a message via AIM to ReggaetonKing
Nice man! Just by looking at your output, you did a great job.
__________________
I would love to change the world, but they won't give me the source code!
ReggaetonKing is offline   Reply With Quote
Old Jun 11th, 2007, 10:44 PM   #3
andro
Professional Programmer
 
Join Date: Oct 2005
Location: California
Posts: 310
Rep Power: 3 andro is on a distinguished road
Send a message via AIM to andro
>> 100 + 2 - 4 + 8 * 50 / 10
530


That should be 138 :o
andro is offline   Reply With Quote
Old Jun 11th, 2007, 10:50 PM   #4
Jessehk
The Oblivious One
 
Jessehk's Avatar
 
Join Date: May 2005
Location: Ontario, Canada
Posts: 644
Rep Power: 4 Jessehk is on a distinguished road
Quote:
Originally Posted by andro View Post
>> 100 + 2 - 4 + 8 * 50 / 10
530


That should be 138 :o
No operator precedence yet. I say all of this in post #1

EDIT: On a semi-related note Andro, I'll be posting the Tic-Tac-Toe code soon (though probably without the GUI stuff).

EDIT2: Thanks reggaeton_king! I'll probably add all the features it's missing once I figure out how to implement them (and then there's the whole floating point number thing...oh no).
__________________
Dr. Zoidberg: [ecstatic] I'm going to a movie... with FRIENDS!
Jessehk is offline   Reply With Quote
Old Jun 12th, 2007, 11:40 PM   #5
Jessehk
The Oblivious One
 
Jessehk's Avatar
 
Join Date: May 2005
Location: Ontario, Canada
Posts: 644
Rep Power: 4 Jessehk is on a distinguished road
I just finished a version with correct operator precedence and the ability to handle fractions (and return results in decimals, but not calculate decimals).

I'll post it once I fix a glitch that causes something like "3 * -2" to cause an error.
Also, I used the Num module in Ocaml, so it can handle arbitrary-length numbers and calculations by default.

EDIT: On the other hand, 3 * -2 should probably cause an error. If I manage to get brackets working (that'll be tough...) then you'll probably have to just type "3 * (-2)".
__________________
Dr. Zoidberg: [ecstatic] I'm going to a movie... with FRIENDS!
Jessehk is offline   Reply With Quote
Old Jun 13th, 2007, 6:32 AM   #6
DaWei
Resident Grouch
 
DaWei's Avatar
 
Join Date: Jun 2005
Posts: 6,453
Rep Power: 10 DaWei is on a distinguished road
3 * -2 shouldn't cause an error. You just need to distinguish between a unary situation and a binary situation.
__________________
Abstraction doesn't make it impossible to write bad code; it makes it possible to write superior code.
Contributor's Corner: Grumpy on C++ Exceptions DaWei on Pointers
DaWei is offline   Reply With Quote
Old Jun 13th, 2007, 8:37 PM   #7
Jessehk
The Oblivious One
 
Jessehk's Avatar
 
Join Date: May 2005
Location: Ontario, Canada
Posts: 644
Rep Power: 4 Jessehk is on a distinguished road
Quote:
Originally Posted by DaWei View Post
3 * -2 shouldn't cause an error. You just need to distinguish between a unary situation and a binary situation.
Done (I think).

I'm attaching a source tarball, including a native executable for x86 (or something), because I know most people don't have the facilities to compile this thing.

EDIT: The executable makes the attachment too big. I'll have to just post the source.

To anyone that's interested: try it out and let me know of any errors or bugs.

EDIT2: Here's a transcript:
CamlCalc version 0.1, Copyright (C) 2007 Jesse H-K
CamlCalc comes with ABSOLUTELY NO WARRANTY.
>> 2 + 3
5
>> 42 - 4 * 3
30
>> 108 * 11 - 4
1184
>> -3 - -2 
-1
>> -3 * -3
9
>> 100^3
1000000
>> 321^123
199580904170858944588683464608694075062943107082809027605498347841561605181074274426665986623764972439807786764739130193391688887305693295698132008415537889753720415793877902074665765736230030622222862498385818118244547668141388643750880896837470534556730014314212951333550122949806119583030226121714710874561
>> 48/50 + 1/2
73/50
>> Fatal error: exception End_of_file
Attached Files
File Type: zip camlcalc-0.1.zip (10.7 KB, 2 views)
__________________
Dr. Zoidberg: [ecstatic] I'm going to a movie... with FRIENDS!
Jessehk is offline   Reply With Quote
Old Jun 14th, 2007, 12:58 AM   #8
DaWei
Resident Grouch
 
DaWei's Avatar
 
Join Date: Jun 2005
Posts: 6,453
Rep Power: 10 DaWei is on a distinguished road
Jesse, I have one unrelated question: when are you going to take that rubber glove off your face? My doctor sticks a lot of stuff up my ass, but his head is not usually one of them. That's my job.
__________________
Abstraction doesn't make it impossible to write bad code; it makes it possible to write superior code.
Contributor's Corner: Grumpy on C++ Exceptions DaWei on Pointers
DaWei is offline   Reply With Quote
Old Jun 14th, 2007, 10:58 AM   #9
pegasus001
Hobbyist Programmer
 
pegasus001's Avatar
 
Join Date: Nov 2006
Location: 163H
Posts: 213
Rep Power: 2 pegasus001 is on a distinguished road
Don`t tell me that retired people do retarded things, kind of the other way???????
__________________
You never test the depth of a river with both feet.
The believer is happy. The doubter is wise.
Free speech carries with it some freedom to listen.
The next generation will always surpass the previous one. It`s one of the never ending cycles of life.
pegasus001 is offline   Reply With Quote
Old Jun 14th, 2007, 11:12 AM   #10
Jessehk
The Oblivious One
 
Jessehk's Avatar
 
Join Date: May 2005
Location: Ontario, Canada
Posts: 644
Rep Power: 4 Jessehk is on a distinguished road
Quote:
Originally Posted by DaWei View Post
Jesse, I have one unrelated question: when are you going to take that rubber glove off your face? My doctor sticks a lot of stuff up my ass, but his head is not usually one of them. That's my job.
Maybe when I get tired of Futurama. Until then, the glove stays.
__________________
Dr. Zoidberg: [ecstatic] I'm going to a movie... with FRIENDS!
Jessehk is offline   Reply With Quote
Reply

Bookmarks

« Previous Thread in Forum | Next Thread in Forum »

Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump




DaniWeb IT Discussion Community
All times are GMT -5. The time now is 10:36 AM.

Powered by vBulletin® Version 3.7.0, Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Copyright ©2007 DaniWeb® LLC