Hasp Documentation

Hasp is an s-expression to Haskell compiler.

About Hasp

The goal of Hasp is to combine the best features of the two best languages on the planet - Haskell and Lisp. Haskell is a strongly typed, lazy, functional language that many people find excellent for quickly developing defect-free software. Lisp has the best macro functionality of any language, due to everything in Lisp being a list. In combining the two, we get Hasp, a language with programmable syntax and pure semantics.

How it works

Hasp supplies two scripts to convert your Hasp code to Haskell: hasps and fasps. hasps reads Hasp expressions from standard input and prints the resulting Haskell code to standard output, with any newlines removed. fasps does exactly the same, but instead of reading until it hits a newline, it keeps reading until it has started reading the next form, and the output it produces can contain newlines.

hasps is inteded for piping into Haskell interpreters, where you manually enter expressions and want to produce a single line of Haskell code. The script gasps does exactly this - it will start up your Haskell interpreter, piping the output from hasps as its input. fasps is intended for compiling files at the command line. If you wanted to compile a file foo.hasp to foo.hs, you would type:

$ fasps < foo.hasp > foo.hs

Hasp forms are very similar to their Haskell counterparts and most share the same name. Here are some quick examples.

If you were to compile the following file with fasps:

(deft main (IO ()) (let= (x 4) (print (* 2 x))))

You would get this as output:

main :: IO ();
main = (let {
    x = 4;
  } in (print ((*) 2 x)));

Factorial in Hasp:

(defn fact (-> Integer Integer)
  ((0) 1)
  ((n) (* n (fact (dec n)))))

Compiled with fasps:

fact :: ((->) Integer Integer);
fact = (\s_gensym_v_0 -> (case (s_gensym_v_0) of {
  (0) -> 1;
  (n) -> ((*) n (fact (dec n)));}));

Hasp puts parenthesis around the forms because it doesn't know about Haskell's precedence rules, and it prints semi-colons after statements to allow multiple statements on the same line.

You can also see that Hasp outputs ((*) n ...) instead of (n * ...). This is not the result of some weird prefix fetish, but is implemented this way so * is a first-class value in Hasp just like any other, whereas for Haskell you have to wrap it in parenthesis eg. (*) for it to be first-class.

Why use it

By using the Lisp syntax, macros are much easier to write (for the uninitiated, this article may give a taster for why macros are useful. You can also take a quick look at the macros provided as standard with Hasp for some examples of what we found them useful for). Currently Template Haskell can be used to write Haskell macros, however these are quite hard to write, since it has to deal with a non-list syntax.

Additionally, editors such as Emacs have very powerful functions that can be used to manipulate s-expressions, which aren't available for normal Haskell syntax.

Hasp integrates with your interpreter, you can type (:hload "foo.hasp") at the gasps prompt to compile and load a Hasp file in one step.

If you dislike some things about Haskell syntax you may want to use Hasp for a cleaner and simpler syntax.

Unlike Haskell, Hasp has a notion of a "top level" - it can remember definitions you've previously made. This makes it much easier to follow along incrementally entering code from a blog post and allows for more rapid development. See this blog post and this one for more details.

Macros

The files std-macros.scm and usr-macros.scm are intended to hold macros. Having this structure prevents Hasp files from filling up with thow-away macros and keeps the macro language entirely separate from the Hasp language (the macro language is Scheme). Any Scheme code is valid in these files, so you can write your macros in other files and use Scheme's load procedure to include them. Both std-macros.scm and usr-macros.scm are automatically loaded by hasps and fasps when they are run.

Here is an example macro from std-macros.scm:

(defmac (let>>= definitions . forms)
  (if (or (null? definitions) (null? (cdr definitions)))
    `(let ,(butlast forms) ,(last forms))
    `(>>= ,(cadr definitions)
      (fn (,(car definitions)) (let>>= ,(cddr definitions) ,@forms)))))

And here is how you could use it in Hasp code:

(let>>= (myfile (readfilei "test")
         a (myfile 0)
         b (myfile 1))
  (print (tup a b)))

Which outputs the following Haskell code:

((>>=) (readfilei "test") (\myfile -> ((>>=) (myfile 0) (\a -> ((>>=) (myfile 1) (\b -> (print (a, b))))))))

This is the Hasp equivalent of do-notation.

Unlike normal macro-programming in Scheme, which has a distinction between macro-time and runtime, for Hasp, Scheme is macro-time, so you can define Scheme procedures along with your Hasp macros and they'll be available when the macro is expanded. Hasp also provides the Scheme procedures gens and gent for generating random variable and type names respectively.

Comparisons to Liskell

Liskell is another approach to allowing Lisp-like metaprogramming for Haskell. The Hasp project initially started when Liskell's website went down, but Hasp is here to stay because it uses a fundamentally different approach from Liskell in achieving its aims. Liskell is implemented on top of the GHC API and the project is at least a few times larger in code size than Hasp. By comparison, Hasp is more like a parasite that can move fluidly between hosts, and this analogy applies at multiple levels, you don't even need to run Hasp on the same machine as the Haskell compiler. At a more concrete level, Hasp can work on at least two different Scheme interpreters and the install script allows installation on other Schemes after a few more questions. Hasp is even more flexible in which Haskell interpreters its code can run on - all of them. Because Hasp deals in Haskell code and not Haskell parse trees, you can feasibly use Hasp with even the most experimental Haskell interpreters.

The most important advantage of Hasp over Liskell is that it is implemented outside of the Haskell compiler, so you can write Hasp code, compile it to Haskell code and run it in pretty much any Haskell interpreter. With Hasp, you are closer to the Haskell - if you are unsure of what your Hasp code looks like to Haskell, you can test it out in hasps.

The most important disadvantage of Hasp over Liskell is that due to the abstracted nature of Hasp's meta-programming stage, true gensyms are not possible. Obviously, we don't think this is a show-stopper, see limitations for more info.

Hasp is more similar to Haskell and Liskell is more similar to the Lisps (Scheme in particular).

Philosophy

Hasp aims to be extremely open and hackable and it is hoped you will see this reflected throughout the project, from the pipeable command-line tools, to the interpreter hooks, to the semantic markup you are viewing now.

This project wants to prove that you don't need to do heavy stuff like hack compilers to implement a meta-programming language. This project wants to create tools that leverage the powerful UNIX philosophy of using standard input and output to provide modular interfaces. Hasp believes that meta-programming is an easy problem and should have a simple solution. At less than 1000 LOC, Hasp is appropriately small and this increases both its maintainability and ease of modification.

Both the Lisp reader and the Haskell interpreter have already been invented - Hasp's job is to suitably combine them into a rich and powerful language.

Indentation reading

A bonus to some and despised by others, Hasp code can also be interpreted by indentation. The install script will ask if you want to enable indentation reading and sets everything up accordingly. So for those wanting to use indentation, here is an example:

def (numStep v s)
  letl (numStep- (n 0))
    cons (+ s (* n v)) (numStep- (inc n))

Note that once inside a list, the reader is no longer indentation-based. For example:

;; This code does not work as expected.

(def (numStep v s)
  letl (numStep- (n 0))
    cons (+ s (* n v)) (numStep- (inc n)))

The above code is not equivalent to the first example, because this form is read as a normal Lisp expression by the Lisp reader. This behaviour means you can compile some fully parenthesised Hasp code using Hasp with indentation enabled, and it'll still work fine.

Scheme style block comments (#| comment ... |#) are allowed, but they must begin on their own line at the right indentation level for the following statement. Single line Scheme comments (; comment ...\n) are allowed anywhere and will be ignored by the reader.

Download

You can also pick up the latest code from source control:

git clone git://github.com/aliclark/Hasp.git

The tarball is only slightly more stable and will also be updated fairly often.

Send any comments, suggestions and questions to anc505@york.ac.uk

Limitations

Because Hasp is a completely separate phase from running a Haskell program, it cannot implement true gensyms, which Liskell can do. Instead, Hasp uses appropriately random variable names, such as s_gensym_20. This is a bit of a hack, but unless you are in the habit of using ugly variable names, it should not affect you. The prefixes used are s_gensym_ and T_gensym_, so you should not begin any variables with these characters.

Here are the bullet-proof rules to using Hasp gensyms safely:

Hasp also needs to know about what is infix for Haskell and what isn't. If you are interfacing with non-standard Haskell defined as infix, you should add the code (definfix myop) to usr-macros.scm, where myop is the name of the operator, and then continue to use it as an ordinary prefix procedure.

Installation

Hasp provideds a basic install script in the project's bin directory which should be able to automate a few configuration things for you. (This script doesn't move files around, it is not analagous to make install.)

$ bin/install

Hopefully this'll work fine. If that fails, email and I'll be happy to help.

Blog

Keep up-to-date on progress by subscribing to the feed.

Hasp in a Lunch Break!

A gentle guide to getting up and running with Hasp.

Notes

Todo

  1. Cook up a macro to replace the Haskell deriving keyword.
  2. Modify the reader to allow Literate Hasp.
  3. Emacs editor mode.
  4. Possibly port the bash scripts to Hasp and provide them as Haskell code.
  5. Use gsc to provide prebuilt binaries.

Bugs

  1. Using Gambit and GHCi 6.10 and if rlwrap is not being used, after exiting gasps, typing is no longer visible in the terminal until it is closed. A workaround is to use an interrupt (^C) to exit gasps instead of EOF (^D).

Credits

Statements

Statements are definitional forms and are not first-class values.

See the typex section for a list of valid typex forms.

(begin . forms)

begin allows multiple statements to be grouped together. It does not denote any sequencing, and is only present in the language to allow macros to return code containing multiple statements.

(defmac (defn name typex . definitions)
  `(begin
    ,(if (null? typex) '(begin) `(:: ,name ,typex))
    ,@(map (lambda (x) `(def ,(cons name (car x)) ,@(cdr x))) definitions)))
(type name typex)

Makes name a synonym for the typex. name can be a symbol (with first character uppercase) or a list with first element as the symbol, followed by its type parameters.

(type (ReadS a) (-> String (List (tup a String))))
(gadt name . signatures)

Creates a GADT. name can be a symbol (with first character uppercase) or a list with first element as the symbol, followed by its type parameters.

(gadt Name (:: A (-> Foo Name)) (:: B (-> Bar Name)) (:: C (-> Baz Name)))
(data name typex)

Creates a data type. name can be a symbol (with first character uppercase) or a list with first element as the symbol, followed by its type parameters.

(data Name (union (A Foo) (B Bar) (C Baz)))
(newtype name typex)

Creates a new data type. name can be a symbol (with first character uppercase) or a list with first element as the symbol, followed by its type parameters.

(newtype Natural (MakeNatural Integer))
(class typex . statements)

Creates a class, with any number of supplied general statements.

(class (Num a)
  (:: + (-> a a a))
  (:: negate (-> a a)))
(instance typex . statements)

Creates an instance of a class, with any number of supplied general statements.

(instance (Typeable (W a)) (def (typeOf _) (typeOf ())))
(module name exporting . forms)

Creates an module, where exporting is a list of names to be exported.

(module Main ()
  (import A)
  (import B)
  (def main (>> A.f B.f)))
(import module . names)

Imports the supplied names from the module. If module is a list of one symbol, then the symbol is used as a qualified name. If module is a list of two symbols, then the first symbol is the module to be imported, and the second is the name it is to be imported as, but the name will not be qualified. If module is a list of one symbol and one list containing a symbol, then the first symbol is the module to be imported, and the second symbol is a qualified name which the module will be imported as. Additionally, if names comprises of a single list, then the symbols of that list will be hidden, and the rest of the module imported.

All import statements must appear at the top of a module definition before any other statements.

(import Prelude (lookup filter foldr)) ;; imports Prelude hiding lookup, filter, and foldr.
(foreign . info)

Defines a foreign interface with the given info.

(foreign import ccall safe "prototypes.h" (:: c_function (IO ())))

Expressions

Expression forms are only found within a statement form. Prefix definitions are provided for the following Haskell infix expressions:

: !! $ $! && * ** + ++ - / /= < <= == =<< > >= >> >>= ^ ^^ :+ ! o or-

(o is the Hasp name for Haskell's composition operator . and or- likewise for Haskell's ||.

These are completely first class and do not need to be wrapped in parenthesis. ie. you could write (map + (list 1 2 3 4)) to return a list of partial + closures.

If you supply more than two arguments to one of the above operators, the arguments will be applied with right association, eg. (+ 1 2 3) is the same as (+ 1 (+ 2 3)).

Hasp doesn't have n+k patterns, but infix constructors like the cons operator : can be used in patterns.

(let declarations . forms)

declarations can be any number of general statements. forms can be any number of general statements, followed by an expression. Any general statements preceding the final expression will be added to the declarations of the let form. The final expression is evaluated within the scope of the declarations. If there are no declarations or statements, the expression is simply evaluated without a let form.

(let ((def dolly breedSheep)) (foo dolly))
(case-of x . forms)

Pattern matches x against the patterns held in the head of each form in forms. If the match succeeds, then the rest of the form is returned as a let expression (allowing definitional forms before a return value).

(case-of (tup a b c)
  ((tup True  x _) (def value x) value) ; def forms are allowed and will be grouped into a let expression.
  ((tup False _ y) y))
(fn formals . forms)

fn behaves much like the lambda of other languages. formals can be a list or a single variable symbol, and can contain pattern matching syntax such as tuples. forms can be any number of general statements, followed by an expression. Any general statements preceding the final expression will be placed in a let form around the expression.

(fn (a b) (+ a b))
(tup . items)

Creates a tuple of fixed size containing the items, which can be of different types. tup forms can be used both within pattern-matching and type signatures, as well as normal code.

(tup (+ 1 2) 3 4)

Typexes

A typex can be any of these forms, or it can be a symbol, or a parameterised type, or a tup of typexes.

(-> . typexes)

-> is defined to work as an infix operator, but should only be used within type signatures.

(:: comb (-> (Maybe a) (-> a (Maybe b)) (Maybe b)))
(union . typexes)

Can be used in a data form to denote a union type.

(data Name (union (A Foo) (B Bar) (C Baz)))
(=> context typex)

context is a list which can contain any number of type parameters referred to by the form. For example, take the following code:

(:: to (=> ((Num a) (Ord a)) (-> a a (List a))))

Here, the list ((Num a) (Ord a)) is a context. It specifies that the type variable a must be an instance of class Num and of class Ord. The rest of the form is now able to use the type variable a in its definition. A context may simply be the empty list if there is no context.

(forall types typex)

types can either be a list of type variables or just a symbol for one type variable.

(forall a (=> ((Show a)) (MkT- a)))
(strict typex)

Defines the given typex to be strictly evaluating.

(strict (STList a))

General

These can be either expressions or statements depending on where they are placed.

(def formals . forms)

Defines a function. formals can be either a symbol naming the function, or a list with name as first element, followed by formal arguments and any pattern matches. forms can be any number of general statements, followed by an expression. Any general statements preceding the final expression will be placed in a let form around the expression.

(def (f x)
  (def (g y) (* 2 y))
  (- (g x) 3))
(:: object typex)

Specifies a type for the given object. object can be the name of a value or the value itself.

(:: comb (-> (Maybe a) (-> a (Maybe b)) (Maybe b)))

Hooks

These are forms specifically intended for the compiler / interpreter and not the language.

(:hs . codes)

codes can be strings or anything convertible to string, which will not be compiled but instead printed unchanged. This allows to directly inline Haskell code.

(:hs ".")
(:scm . forms)

forms are Scheme code to be evaluated at compile time. If Hasp is running over an interpreter, this allows easy access to the underlying Scheme. A tuple is returned with the values of forms.

(:scm (map (lambda (x) (* x x)) '(1 2 3 4 5))) ;; outputs ([1, 4, 9, 16, 25])
(:hload . filenames)

Compiles the given filenames as Hasp code and outputs the Haskell to a new filename. If the input filename ends in ".hasp", the new file will have ".hasp" replaced with ".hs", otherwise, ".hs" is appended to the original filename to give the new name. eg. "foo.hasp" -> "foo.hs", "foo.txt" -> "foo.txt.hs". hload will then print out a command for the Haskell interpreter to load the files.

(:hload "src/std-hasp.hasp") ;; results in src/std-hasp.hs being created with the Haskell code.

Comments

Can be used add comments to the resulting Haskell code. These should only be used at the top-level, or in a module, class or instance form.

(-- . text)

A single line comment, with any newlines removed from the text.

(-- "These are too useful not to have.")
(~~ . text)

Multi-line comments.

(~~ "This is a
     multi-line comment.")

Standard Macros

Macros combine the previously defined elements of the language in new ways.

(= . forms)

Synonym for def.

(= (square x) (* x x))

;; is equivalent to:

(def (square x) (* x x))
(if . forms)

Synonym for if-.

(if (> x 2) (/ x 2) (* x 0.2))

;; is equivalent to:

(if- (> x 2) (/ x 2) (* x 0.2))
(list . items)

This creates a list of any number of items of the same type.

(list (+ 1 2) 3 4)

;; is equivalent to:

(: (+ 1 2) (: 3 (: 4 Nil)))
(deft formals typex . forms)

Exactly the same as a def form except that it accepts a typex immediately after the formals.

(deft (dec x) (=> ((Num a)) (-> a a)) (- x 1))

;; is equivalent to:

(:: dec (=> ((Num a)) (-> a a)))
(def (dec x) (- x 1))
(defn name typex . definitions)

Defines a function denoted by symbol name. If typex is not empty, it is used in a type-signature form. Each definition is a list with first element being the formal arguments of the definition, and the rest of the definition being the function forms, as given to the def form.

(defn bar (-> Int Int Int)
  ((x 0) x)
  ((x y) (* x y)))

;; is equivalent to:

(:: bar (-> Int Int Int))
(def (bar s_gensym_v_0 s_gensym_v_1)
  (case-of (tup s_gensym_v_0 s_gensym_v_1)
    ((tup x 0) x)
    ((tup x y) (* x y))))
(let= definitions . forms)

Provides local variable binding around forms. definitions is a flat list of formals and their values. The formals can be either a symbol or a list of name and formal arguments. Each of these formal to value duos will be placed in a def form. forms is any number of statements followed by an expression, as similar to the let form. let= is similar to the letrec of Lisps (but without parenthesis around each variable declaration).

(let= (x (* 2 4)
       y (+ x 4))
  (* x y))

;; is equivalent to:

(let ((def x (* 2 4))
      (def y (+ x 4)))
  (* x y))
(letl formal . body)

Similar to the named let of Scheme, except that the loop name is at the head of the variable declaration list.

(def (step v s e)
  (letl (step- (n 0))
    (def next (+ s (* n v)))
    (if- (> next e) Nil (cons next (step- (inc n))))))

;; is equivalent to:

(def (step v s e)
  (let ((def (step- n)
          (def next (+ s (* n v)))
          (if- (> next e) Nil (cons next (step- (inc n))))))
    (step- 0)))
(let>>= definitions . forms)

This is Hasp's equivalent of do-notation. It behaves very similarly to the let= notation, but for monadic types. If there are more than one forms, the behaviour is the same as with let= - all but last of the forms will be grouped into a let expression around the last form.

Unlike Haskell's do-notation, if a pattern binding fails, this will lead to a normal pattern binding error, whereas with do-notation, if a binding fails, the Monad fail function is called with an error string. This behaviour can be reproduced by manually checking correct input values and calling fail if incorrect.

(let>>= (myfile (readfilei "test")
         a (myfile 0)
         b (myfile 1))
  (print (tup a b)))

;; is equivalent to:

(>>= (readfilei "test")
  (fn (myfile)
    (>>= (myfile 0)
      (fn (a)
        (>>= (myfile 1)
          (fn (b)
            (print (tup a b))))))))
(cond . tests)

A simple wrapper around the condList function, to save time on typing lists and tuples. The last parameter to cond should be an expression to use if all tests fail.

(cond
  ((> a b) (def c (+ a 2)) (* c b))
  ((> a 3) (* a 2))
  (- a b))

;; is equivalent to:

(condList
  (list
    (tup (> a b) (let ((def c (+ a 2))) (* c b)))
    (tup (> a 3) (* a 2)))
  (- a b))
(case find . forms)

A simple wrapper around the caseList function, to save time on typing lists and tuples. The last parameter to case should be an expression to use if all tests fail. Please note that unlike Haskell's case-of syntax, this form does not do pattern-matching on the left hand side, it is purely a comparison.

(case x
  (1 "A")
  (2 "B")
  (3 "C")
  "D")

;; is equivalent to:

(caseList x
  (list
    (tup 1 "A")
    (tup 2 "B")
    (tup 3 "C"))
  "D")
(defaccessors name . fields)

Creates acessors for the fields of the data type name. Accessors can be used with the Haskell functions get, set, getter and setter. You can use the underscore symbol _ in place of a field name if you don't want to define an accessor for it.

(defaccessors Settings width height mineCount)

;; is equivalent to:

(def width (tup (fn ((Settings width _ _)) width) (fn ((Settings _ height mineCount) width) (Settings width height mineCount))))
(def height (tup (fn ((Settings _ height _)) height) (fn ((Settings width _ mineCount) height) (Settings width height mineCount))))
(def mineCount (tup (fn ((Settings _ _ mineCount)) mineCount) (fn ((Settings width height _) mineCount) (Settings width height mineCount))))
(defgetters name . fields)

Creates getters for the fields of the data type name. You can use the underscore symbol _ in place of a field name if you don't want to define a getter for it.

(defgetters Sheep name mother father)

;; is equivalent to:

(def name (fn ((Sheep name _ _)) name))
(def mother (fn ((Sheep _ mother _)) mother))
(def father (fn ((Sheep _ _ father)) father))

Standard Haskell

Hasp provides some Haskell definitions for a few things which are syntax in Haskell, and a few things which are used incredibly often. Remember, to use foo' in Hasp, you would write foo- instead.

(type (List a) (:hs "[a]"))

This is the type constructor for a list of elements with type a and can be used within type signatures.

(List (tup Bool a))

The symbol Nil is used as the unary constructor for the empty list and so can be used to pattern match against an empty list, or for consing onto.

(: 1 (: 2 (: 3 Nil)))
(deft inc (=> ((Num a)) (-> a a)) (+ 1))

Increment by one.

(inc 4)   ;; 5
(inc 4.2) ;; 5.2
(deft (dec x) (=> ((Num a)) (-> a a)) (- x 1))

Decrement by one.

(dec 6)   ;; 5
(dec 6.7) ;; 5.7
(deft zero (=> ((Num a)) (-> a Bool)) (== 0))

Test whether a number is zero.

(zero 0)   ;; True
(zero 0.0) ;; True
(zero 2)   ;; False
(deft cons (-> a (List a) (List a)) (:hs "(:)"))

cons is provided as an alternative name for the cons operator, :

(cons 1 (list 2 3 4))
defn if- (-> Bool a a a)

Replacement for if-then-else syntax

(if- (> 3 2) "3 is greater" "3 is not greater")
defn condList (-> (List (tup Bool a)) a a)

The second argument to condList should be default value to return if all of the tuple Bools return False. Replacement for guard syntax.

(condList
  (list
    (tup (> a b) (* a 2))
    (tup (< a b) (* a 3)))
  (- a b))
defn caseList (=> ((Eq a)) (-> a (List (tup a b)) b b))

The third argument to caseList should be default value to return if all of all comparisons between the first argument and the tuple value are not equal. Replacement for case-of syntax.

(caseList x
  (list
    (tup 1 "A")
    (tup 2 "B")
    (tup 3 "C"))
  "D")
(type (Getter whole part) (-> whole part))

A basic type synonym for a getter function.

(type (Setter whole part) (-> whole part whole))

A basic type synonym for a setter function.

(type (Accessor whole part) (tup (Getter whole part) (Setter whole part)))

A basic type synonym for an accessor - a tuple of a getter function and a setter function.

defn getter (-> (Accessor n a) (Getter n a))

Returns the getter function of an accessor.

((getter width) square)
defn setter (-> (Accessor n a) (Setter n a)

Returns the setter function of an accessor.

((setter width) square 5)
defn accessor (-> (Getter n a) (Setter n a) (Accessor n a))

Creates an accessor from a getter and a setter.

defn get (-> n (Accessor n a) a)

Applies the getter of an accessor to a data object.

(get square width)
defn set (-> n (Accessor n a) a n)

Applies the setter of an accessor to a data object.

(set square width 5)

Indentation Reader

The indentation reader provides a keyword to allow grouping of items.

items

If the indentation reader sees the items keyword, it will group any further indented forms into one list, without the items symbol. This allows a fully indentation oriented code, making it possible to write hasp code with zero parenthesis. This can be useful if you have some hasp code which needs to run with indentation needed, but want to put it in a sub list, without adding parenthesis to all of the inner forms.

let
  items
    def bar 4
    def baz (* bar 2)
  + bar baz

Scripts

Found in the bin subdirectory

hasps

hasps reads a Hasp expression from standard input, and prints the equivalent Haskell code to standard output in a single line. The hasps script supplied by default uses the Gambit interpreter, gsi. If you are using another Scheme interpreter, please edit the hasps script accordingly, or run the install script. hasps uses the indentation reader by default but this can also be changed. hasps will use rlwrap if you have it.

hasps automatically loads sugar-read.scm and the std and usr macro files.

You can run bin/hasps without redirection to test out bits of Hasp code and see the Haskell code it produces.

fasps

fasps is very similar to hasps, but is intended for taking input from files and passing output to files. It prints newlines in its output and does not finish reading an expression until it starts reading the next one.

You can use fasps to compile files at the command line, eg.

$ bin/fasps < test/sheep.hasp > test/sheep.hs
gasps

gasps passes its standard input to hasps and pipes the standard output from hasps into GHCi (or another interpreter), so you can still use your Haskell interpreter with Hasp. If you use Hugs or another interpreter, you can specify to use that instead in the install script. gasps will use rlwrap if you have it.

For interactive testing, open a prompt and do:

$ bin/gasps

This should act like a normal GHCi / Hugs session, but using Hasp code. For interpreter commands, like :load foo.hs, use (:load "foo.hs").

Gotcha: While testing at the REPL, you may want to take input, eg. with:

Hasp> (>>= (getLine) putStr)

However, since anything you input is interpreted as Hasp code, you can't simply enter a line, you have to wrap it with :hs, like so:

Hasp> (>>= (getLine) putStr)
(:hs "this is foo")
this is foo
install

install asks a couple of questions and tries to generate the fasps, hasps-helper, gasps-helper, and preload.scm files automagically.

hasps-helper

Separate script used by hasps allow it to be rlwrapped if available.

gasps-helper

Separate script used by gasps allow it to be rlwrapped if available.

hasp-build-load-file

Used by hasps and fasps to combine the main source files into one source file, load-file.scm, which can then be loaded by the Scheme interpreter, removing the need to change directory beforehand.

Source files

macros.scm

macros.scm contains some Scheme macros used by hasp.scm, std-macros.scm, and usr-macros.scm.

hasp.scm

hasp.scm contains the Scheme procedure compile which converts a Hasp expression to Haskell code. It also contains the procedure acc to create a new Haskell file with code generated from an inputted Hasp file. hasp.scm uses 5 implementation-specific procedures: (make-table), (table-ref table key), (table-set! table key value), (force-output port), and (include filepath). If these are not defined by your implementation, please make sure you define them appropriately before-hand (the preload.scm file is specifically used for this purpose). The install script tries to do this for you. If you don't have an equivalent force-output procedure, you can use (lambda x x) instead, but the gasps script might not work properly.

acc-term.scm

acc-term.scm contains something like (acc-flat (current-input-port) (current-output-port) user-reader) and is used by hasps to help provide a command-line interface to Hasp.

acc-file.scm

acc-file.scm is very similar to acc-term.scm, but is used by fasps instead, so it will not flatten its output.

std-macros.scm

std-macros.scm contains some basic Hasp macros that Hasp users are likely to want to use in their code.

usr-macros.scm

usr-macros.scm contains your own Hasp macros, make it up as you go along. See std-macros.scm for examples of how to write Hasp macros.

sugar-read.scm

sugar-read.scm contains an indentation-aware read procedure. See SRFI 49 for details. Please note however that the reader provided has been heavily modified with improvements.

preload.scm

preload.scm should contain aliases to implementation specific procedures: make-table, table-set!, table-ref, force-output, and include, plus a definition of define-macro if it is not already defined. The install script will try to autogenerate this file.

load-file.scm

Is used by hasp-build-load-file to store the bulk of the Scheme source code for loading.

std-hasp.hasp

std-hasp.hasp produces std-hasp.hs

std-hasp.hs

std-hasp.hs defines Haskell functions for things which are syntax in Haskell, but probably shouldn't be (such as the if-then-else statement).