Programming Forums
User Name Password Register
 

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

Reply
 
Thread Tools Display Modes
Old Mar 15th, 2007, 8:02 PM   #1
Mad_guy
Hobbyist Programmer
 
Mad_guy's Avatar
 
Join Date: Oct 2004
Location: Sandstorm, Techno Club
Posts: 239
Rep Power: 4 Mad_guy is on a distinguished road
Send a message via AIM to Mad_guy Send a message via MSN to Mad_guy
Haskell fun: hsh -- a busybox spinoff in haskell

So I was trying to figure out exactly what to do with Haskell, as I wanted to experiance the language. I read Don Stewarts excellent "Programming Haskell" 3 part series and it lead me to try and write clones of unix commands in Haskell. So far my program implements roughly 11 commands.

Examples:
[austin@continuum ~]$ hsh -v
haskell hsh v0.4, compiled with ghc-6.6, on linux-i386
[austin@continuum ~]$ hsh cat < dummy.txt
hi
i'm austin
[austin@continuum ~]$ hsh tac < dummy.txt
i'm austin
hi
[austin@continuum ~]$ hsh wc < dummy.txt
2
[austin@continuum ~]$ hsh uname
Linux continuum 2.6.20-ARCH #1 SMP PREEMPT Sat Mar 10 10:12:25 CET 2007 i686
[austin@continuum ~]$ hsh whereis gcc
gcc: /usr/bin/gcc
[austin@continuum ~]$ hsh ls /tmp
...
[austin@continuum ~]$ echo -e "dont mess with me\ndue" | hsh grep "do"
dont mess with me
[austin@continuum ~]$

It also implements other commands such as rmdir, cp, rm, mv, mvdir (had to seperate the two), etc. (grep is my favorite.)

For anybody just wanting the source code:
http://paste.lisp.org/display/37726#4

If anybody would like to try it out, especially on other compilers (which I would appreciate,) you can get a source tarball here:
http://austin.youareinferior.net/scr...0070315.tar.gz (linux/bsd only)

If you try it, tell me what haskell compiler and version you use; I have only tested this on GHC 6.6 (so any hugs98 or nhc98 users are appreciated.) Compilation instructions are included in the README file, all you need is a Compiler and Cabal (I decided to try it for this and absolutely loved it as a build system. I wish more were like it.)


If anybody wants to try it but doesn't have a Haskell compiler, I'm not giving out binaries right now. I need to clean up the option code *a lot* (I figured out Haskell's Getopt, so I'll use that for the option code in the v0.5 release.) Otherwise, you wouldn't probably really know how to work it without looking at the source (at least that's how I feel.) If anybody wants to upload a .tar.gz with it in the topic, that'd be fine as well.
__________________
os: mac os 10.5.4
revision control: git
editor: emacs

site
Mad_guy is offline   Reply With Quote
Old Mar 16th, 2007, 3:29 PM   #2
Arevos
Programming Guru
 
Arevos's Avatar
 
Join Date: Aug 2005
Location: England
Posts: 1,499
Rep Power: 5 Arevos is on a distinguished road
I had some fun pouring through your code, and I hope you won't be offended if I say that I noticed a few areas that look like they could be improved. All those "suceeds" add up to a lot of redundancy, and I wanted to see if Haskell had some mechanism of cutting them down.

I remembered reading an article on Haskell error handling, and I decided that throwDyn and catchDyn looked the way to go. I wrote out some test code, and after solving the most obvious errors, I eventually got:
test.hs:6:5:
    Can't make a derived instance of `Typeable ShellError'
    (You need -fglasgow-exts to derive an instance for this class)
    When deriving instances for type `ShellError'
Adding that extension made it work, but it got a long "hmmm" from me. I decided to investigate why this error handling needed a specific compiler flag set, and eventually came across this article. This gist of it seems to that it some advanced typing mojo for Haskell that is only supported by later versions (so Hugs doesn't support it, but GHC 6.4 and above does). Since GHC supports it, and since I couldn't find any error handling mechanism better than throwDyn/catchDyn, I decided to roll with it.

My first step, then, was to define an error type to handle all potential errors:
data ShellError = UsageError | AppError String String
    deriving (Typeable)
The error can either be a UsageError (the user put in the wrong arguments), or an AppError (there's something wrong with one of our embedded applications). This is essentially just an adaptation of the code in the Haskell error handling article.

Next, I put the ShellError type in the Show type class. This will allow me to use the "show" function on my errors:
usage = "usage: hsh [COMMANDS...] [OPTIONS]"

instance Show ShellError where
    show UsageError = usage
    show (AppError app msg) = printf "%s: %s" app msg
So, if we show the UsageError, we get a little usage message, and if we show the AppError, we get a formatted message (like "ls: can't find dir").

Next, I'll use catchDyn to create a simple wrapper function:
handleErrors m = catchDyn m handler
    where handler :: ShellError -> IO ()
          handler err = hPutStrLn stderr (show err) >>
                        exitWith (ExitFailure 1)
This will run the IO monad passed to it, and if an error occurs, it'll print it to STDERR and exit with failure code 1. I had to explicitly specify the type here, so Haskell knew what error type I wanted to handle.

Armed with handleErrors, I wrapped my main function in it:
main = handleErrors (getArgs >>= parse)
And wrote a quick parse function to test it:
parse []        = throwDyn UsageError
parse ("ls":xs) = ls xs
The UsageError worked fine, so next I decided to see if the AppError would work. Since I figured that I'd be checking for files, directories etc. quite a bit, I create a quick "ensure" function to make my life easier:
ensure :: IO Bool -> ShellError -> IO () -> IO ()
ensure pred err m = do x <- pred
                       if x then m else throwDyn err
This function takes in an error type, a predicate, and some code to run. If the predicate is true, it runs the code, but if it is false, it throws the error instead Thanks to some magic I don't fully understand, when the error is thrown the IO () type is still returned. I'll have to figure out what exactly is going on here, but it's rather neat.

Anyway, all that's left is my ls function:
ls []     = ls ["."]
ls (x:xs) =
    ensure (doesDirectoryExist x) dirError $
        getDirectoryContents x >>= mapM_ putStrLn
    where dirError = AppError "ls" (printf "directory '%s' does not exist" x)
Functionally similar to yours, Mad_guy, but it uses mapM_ to map putStrLn to each file in the directory, instead of using a separate recusive function. It also makes use of the "ensure" function, and the error handling.

Anyway, that's the best I could come up with, but I'm still a Haskell newbie too, so I'm sure this can be improved further. I'm not really that happy with the "ensure" function. It doesn't seem that neat to me.

Here's the full source for reference:
import IO
import System.Exit
import System.Directory
import System.Environment
import Data.Typeable
import Control.Exception
import Text.Printf

usage = "usage: hsh [COMMANDS...] [OPTIONS]"

data ShellError = UsageError | AppError String String
    deriving (Typeable)

instance Show ShellError where
    show UsageError = usage
    show (AppError app msg) = printf "%s: %s" app msg

handleErrors m = catchDyn m handler
    where handler :: ShellError -> IO ()
          handler err = hPutStrLn stderr (show err) >>
                        exitWith (ExitFailure 1)

main = handleErrors (getArgs >>= parse)

parse []        = throwDyn UsageError
parse ("ls":xs) = ls xs

ensure :: IO Bool -> ShellError -> IO () -> IO ()
ensure pred err m = do x <- pred
                       if x then m else throwDyn err

ls []     = ls ["."]
ls (x:xs) =
    ensure (doesDirectoryExist x) dirError $
        getDirectoryContents x >>= mapM_ putStrLn
    where dirError = AppError "ls" (printf "directory '%s' does not exist" x)
Arevos is offline   Reply With Quote
Old Mar 16th, 2007, 4:10 PM   #3
Mad_guy
Hobbyist Programmer
 
Mad_guy's Avatar
 
Join Date: Oct 2004
Location: Sandstorm, Techno Club
Posts: 239
Rep Power: 4 Mad_guy is on a distinguished road
Send a message via AIM to Mad_guy Send a message via MSN to Mad_guy
Thanks for the comments. :>

Last night I was talking to a friend and decided to totally rewrite it. Renaming it to version 1.0 was pretty much obligatory. The new version is substantially different. Instead of having to hard code commands in, the new version is extensible. All you have to do is provide a name that the command will respond to and a callback function. Write your function in a module, and when someone calls say, "hsh ls" it'll execute the LS function. You only have to import your module into a special core module, and add an entry to a list for your command to work (and if you want to distribute your program as source using Cabal's "setup.hs sdist," you must slightly modify the hsh.cabal file, but that's all.) When your command is integrated, configure and build (Cabal script provided, just run 'runhaskell Setup.hs ...')

The usefulness of this is that the main core really doesn't "exist" as far as commands go. All commands take a type [String] and return IO () (I added type signatures to most of the base code, but this shouldn't really be a limitation.) The [String] is the command line arguments passed to your program. This means the commands can be as simple or complex as you want. For example, you could write a new grep implementation that instead of just supporting searching like in my example, it uses the System.Console.GetOpt library and accepts much more complex arguments. This also is a benefit as the core (there're only 4 files in the core, the other ones in the source tree are command modules to be included) is cross platform, so you could write unix/win32 specific commands and modules. The 1.0 distribution, however, comes with some modules that use non-portable libraries, so don't expect this to work on Windows right out of the box (this development was done on my linux system.)

I figured this was the best way to accomplish having an extensible program, just let the users build their commands in if they need them. If Haskell was more easily embeddable in Haskell, I probably would have allows the use of literate haskell source code. I would have followed one of the many tutorials on Scheme in Haskell, but this would really limit what you'd be able to write. So just building modules into the executable seemed like the best code.

The code is still pretty naive and basic, so for maybe a 1.1 release I'll beef up the error handling a lot more. I really just need a README before the 1.0 release is done so you could write a module, but here's the source code, I'll assume you can figure it out (building it is just like building any other cabal application):

http://austin.youareinferior.net/scr...0070316.tar.gz
__________________
os: mac os 10.5.4
revision control: git
editor: emacs

site
Mad_guy 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

Similar Threads
Thread Thread Starter Forum Replies Last Post
Haskell Arevos Other Programming Languages 25 Jun 3rd, 2007 4:34 PM
Haskell ideas? Mad_guy Other Programming Languages 3 Feb 5th, 2007 9:16 PM




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

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