diff --git a/GL.hs b/GL.hs
new file mode 100644
--- /dev/null
+++ b/GL.hs
@@ -0,0 +1,74 @@
+-- Here we specify the OpenGL code to draw the nodes, and where the ports of the nodes are positioned, which is needed for the layouting.
+module GL () where
+
+import Graph
+import GraphRewriting.Layout.PortSpec
+import qualified Graphics.UI.GLUT as GL
+import GraphRewriting.GL.Render
+
+
+-- Here we specify for each node type the position of its ports and the direction an edge attached to the port is to be oriented. Here we only use the 'sameDir' function that orients the edge in the same direction as the port, i.e. if the (only) port of the root node type (R) is south, also the edge attached to the port is to point south.
+instance PortSpec SKI where
+	portSpec node = let sd = sameDir in case node of
+		R {} → [sd s]
+		A {} → [sd n, sd (sw*0.7), sd (se*0.7)]
+		I {} → [sd n]
+		E {} → [sd s]
+		D {} → [sd nw, sd ne, sd s]
+		V {} → [sd n]
+		K0 {} → [sd n]
+		K1 {} → [sd n, sd s]
+		S0 {} → [sd n]
+		S1 {} → [sd n, sd s]
+		S2 {} → [sd n, sd (sw*0.7), sd (se*0.7)]
+		where -- points of compass
+			n = Vector2 0 1
+			w = Vector2 (-1) 0
+			e = Vector2 1 0
+			s = Vector2 0 (-1)
+			sw = Vector2 (-1) (-1)
+			se = Vector2 1 (-1)
+			nw = Vector2 (-1) 1
+			ne = Vector2 1 1
+
+-- Here we specify the OpenGL code that draws a node. Refer to the documentation of the OpenGL and GLUT packages on how to do this.
+instance Render SKI where render = renderNode
+
+renderNode R {} = return ()
+renderNode node = drawPorts node >> case node of
+	A {}  → drawNode "A"
+	I {}  → drawNode "I"
+	E {}  → drawNode "E"
+	D {}  → GL.preservingMatrix $ GL.renderPrimitive GL.LineLoop $ do
+		vertex2 (0,-1)
+		vertex2 (-1,1)
+		vertex2 (1,1)
+	V {name = n}  → drawNode n
+	K0 {} → drawNode "K"
+	K1 {} → drawNode "K"
+	S0 {} → drawNode "S"
+	S1 {} → drawNode "S"
+	S2 {} → drawNode "S"
+
+drawPorts ∷ SKI -> IO ()
+drawPorts = mapM_ drawPort . relPortPos
+
+circle r1 r2 step = mapM_ vertex2 vs where
+	is = take (truncate step + 1) [0, i' .. ]
+	i' = 2 * pi / step
+	vs = [ (r1 * cos i, r2 * sin i) | i <- is ]
+
+drawPort pos = GL.preservingMatrix $ do
+	GL.translate $ vector pos
+	GL.renderPrimitive GL.Polygon (circle 0.15 0.15 10)
+
+drawNode label = do
+	GL.renderPrimitive GL.LineLoop (circle 1 1 20)
+	drawString label
+
+drawString label = GL.preservingMatrix $ do
+	GL.translate $ vector2 (-0.3,-0.3)
+	GL.scale 0.0065 0.0065 (0 ∷ GL.GLdouble)
+	GL.renderString GL.MonoRoman label
+
+-- Now you can see in Main.hs how it all is tied together to make an actual graphical application out of this.
diff --git a/Graph.hs b/Graph.hs
new file mode 100644
--- /dev/null
+++ b/Graph.hs
@@ -0,0 +1,104 @@
+-- To understand how to use the graph-rewriting library this module is a good point to start reading. Some fundamentals are covered in the documentation of the "GraphRewriting" module.
+module Graph where
+
+-- The 'View' abstraction will appear throughout the modules, see "GraphRewriting" and "Data.View".
+import Data.View
+-- This gives us basic data types like 'Port' or 'Edge'.
+import GraphRewriting.Graph
+-- We need graph modification functions in order to build a graph from the term.
+import GraphRewriting.Graph.Write
+-- Most of the rewriting rules we specify are interaction net reductions, for which a convenient pattern is predefined.
+import GraphRewriting.Pattern.InteractionNet
+-- This module specifies the term representation of an SKI expression (abstract syntax tree) 
+import qualified Term
+
+
+-- The signature of the graph is determined by the node type we provide. For each node constructor we define as record fields a fixed collection of ports. Here we name ports attached at the top of the nodes input ports and nodes at the bottom output ports.
+data SKI
+	-- This node is supposed to occur exactly once in the graph and correpsonds to the root of the term
+	= R {out ∷ Port} -- root
+	-- An applicator. It's left-hand subgraph (out1) denotes the expression to which the expression represented by the right-hand subgraph is applied.
+	| A {inp, out1, out2 ∷ Port}
+	-- The identity combinator.
+	| I {inp ∷ Port}
+	-- An eraser. This node type is used to delete the subgraph discarded by the K combinator.
+	| E {inp ∷ Port}
+	-- A duplicator is used to implement sharing in the SKI combinator calculus
+	| D {inp1, inp2, out ∷ Port}
+	-- A free variable
+	| V {inp ∷ Port, name ∷ String}
+	-- Here we implement the SKI combinators in a very fine-grained manner, namely a combinator has to accumulate its arguments one-by-one before it can be applied. That is why we have two variants of the K combinator: K0 (no arguments accumulated) and K1 (saturated).
+	| K0 {inp ∷ Port}
+	| K1 {inp ∷ Port, out ∷ Port}
+	-- The same goes for the S combinator, which takes even one more parameter.
+	| S0 {inp ∷ Port}
+	| S1 {inp ∷ Port, out ∷ Port}
+	| S2 {inp ∷ Port, out1 ∷ Port, out2 ∷ Port}
+
+-- While it is very convenient to specify the nodes' ports as record fields as above it does not reveal the graph structure to the library. Therefore we have to provide some boilerplate code to expose the ports, for which we use the 'View' abstraction. In the future some Template Haskell might be included in the library to avoid this effort.
+instance View [Port] SKI where
+	-- For each node type we simply have to return a list containing all the ports.
+	inspect ski = case ski of
+		R {out = o}                        → [o]
+		A {inp = i, out1 = o1, out2 = o2}  → [i,o1,o2]
+		I {inp = i}                        → [i]
+		E {inp = i}                        → [i]
+		D {inp1 = i1, inp2 = i2, out = o}  → [i1,i2,o]
+		V {inp = i}                        → [i]
+		K0 {inp = i}                       → [i]
+		K1 {inp = i, out = o}              → [i,o]
+		S0 {inp = i}                       → [i]
+		S1 {inp = i, out = o}              → [i,o]
+		S2 {inp = i, out1 = o1, out2 = o2} → [i,o1,o2]
+	-- But we also need to provide means for the library to update these ports.
+	update ports ski = case ski of
+		R  {} → ski {out = o}                       where [o]       = ports
+		A  {} → ski {inp = i, out1 = o1, out2 = o2} where [i,o1,o2] = ports
+		I  {} → ski {inp = i}                       where [i]       = ports
+		E  {} → ski {inp = i}                       where [i]       = ports
+		D  {} → ski {inp1 = i1, inp2 = i2, out = o} where [i1,i2,o] = ports
+		V  {} → ski {inp = i}                       where [i]       = ports
+		K0 {} → ski {inp = i}                       where [i]       = ports
+		K1 {} → ski {inp = i, out = o}              where [i,o]     = ports
+		S0 {} → ski {inp = i}                       where [i]       = ports
+		S1 {} → ski {inp = i, out = o}              where [i,o]     = ports
+		S2 {} → ski {inp = i, out1 = o1, out2 = o2} where [i,o1,o2] = ports
+
+-- Since we want to make use of interaction net reductions (using the 'activePair' pattern) we need to specify the principal port for each node type in the form of an index into the port list above.
+instance INet SKI where
+	principalPort ski = case ski of
+		R {out = o}                        → 0
+		A {inp = i, out1 = o1, out2 = o2}  → 1
+		I {inp = i}                        → 0
+		E {inp = i}                        → 0
+		D {inp1 = i1, inp2 = i2, out = o}  → 2
+		V {inp = i}                        → 0
+		K0 {inp = i}                       → 0
+		K1 {inp = i, out = o}              → 0
+		S0 {inp = i}                       → 0
+		S1 {inp = i, out = o}              → 0
+		S2 {inp = i, out1 = o1, out2 = o2} → 0
+
+-- In "Term" a little SKI term parser is given. The code below implements a small compiler that translates the abstract syntax tree into a graph. Here you can see how primitive graph transformation functions like 'newNode' and 'newEdge' can be used to build a graph inside the 'GraphRewriting.Graph.Rewrite' monad. Also it shows how an edge can be attached to a node's port, simply by assigning it to the corresponding record field.
+fromTerm ∷ Term.Expr → Graph SKI
+fromTerm term = flip execGraph emptyGraph $ do
+	e ← compile term
+	newNode R {out = e}
+
+compile ∷ Term.Expr → Rewrite SKI Edge
+compile term = do
+	e ← newEdge
+	case term of
+		Term.A f x → do
+			ef ← compile f
+			ex ← compile x
+			newNode A {inp = e, out1 = ef, out2 = ex}
+		Term.S → newNode S0 {inp = e}
+		Term.K → newNode K0 {inp = e}
+		Term.I → newNode I {inp = e}
+	--	Term.B → newNode B {inp = e}
+	--	Term.C → newNode C {inp = e}
+		Term.V v → newNode V {inp = e, name = v}
+	return e
+
+-- Continue reading in the "Rules" module.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,10 @@
+Copyright (c) 2010, Jan Rochel
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+* Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,65 @@
+-- Given the graph signature in Graph.hs, the rewrite rules in Rules.hs and the layouting information and rendering code in GL.hs we can tie it together to obtain an interactive, graphical application to see our rewriting system in action.
+module Main where
+
+import GraphRewriting.GL.Render
+import GraphRewriting.GL.UI as UI
+import Term (parseFile)
+import Graph
+import GL ()
+import Rules
+import GraphRewriting
+import GraphRewriting.Graph.Write.Unsafe as Unsafe
+import GraphRewriting.Layout.Coulomb
+import GraphRewriting.Layout.SpringEmbedder
+import GraphRewriting.Layout.Gravitation
+import GraphRewriting.Layout.Wrapper
+
+
+-- This is annoying. You have to define how your wrapped nodes can be rendered namely by using the rendering code specified for the wrapped values. Maybe I will get rid of this requirement somehow. What is the wrapping about? In order to layout the nodes of your graph there has to be layouting information (position, orientation) associated to it, which we did not do in Graph.hs. Therefore we use the 'wrapGraph' function (see below) to augment our nodes with this information.
+instance Render (Wrapper SKI) where render = render . wrappee
+
+main ∷ IO ()
+main = do
+	 -- first we need to initialise the GUI (GLUT really), which returns the program name and the arguments supplied to the program call (minus any GLUT options).
+	(prog,args) ← UI.initialise
+	file ← case args of
+		[f] → return f
+		___ → error "usage: ski [GLUT-options] <file>"
+	term ← parseFile file
+	let graph = fromTerm term -- here we compile the parsed term into a graph (see Graph.hs)
+	-- and finally we run the GUI.
+	UI.run
+		40                -- Here we specify that layoutStep should be applied 40 times to the initial random layout of the graph before displaying it.
+		id                -- We use the identity projection. You don't have to care about this.
+		layoutStep        -- This specifies the modification of the graph's layout, which is applied in every frame of the animation.
+		(wrapGraph graph) -- Our graph, wrapped in paper.
+		ruleTree          -- The rule menu you will see in the top left corner of the window.
+
+-- Here we specify the forces that are applied in each layout step. You can play around with the values, but strange things may happen (correction: strange things already DO happen).
+layoutStep = do
+	withNodes $ \n → do
+		(cgf, cf, sf, rot) ← readOnly $ do
+			cgf ← centralGravitation n -- this is needed in order to prevent unconnected subgraphs to drift apart infinitely
+			cf ← coulombForce n        -- a repulsing force between each pair of nodes
+			sf ← springForce 1.5 n     -- this tries to push nodes into the position as dictated by the edges connected to other nodes.
+			rot ← angularMomentum n    -- this tries to rotate nodes such that the angular forces exercised by the edges upon the nodes are minimised.
+			return (cgf, cf, sf, rot)
+		-- combine all the forces specifying for each of them a function that maps the distance the force bridges to a strength.
+		Unsafe.adjustNode (Position . sf (*0.9) . cgf (*0.01) . cf (\x → min (100/(x^2+0.1)) 10) . position) n
+		-- We do not rotate our nodes here, since an SKI combinator graph can nicely be drawn top-down. Uncomment, if you like.
+		-- Unsafe.adjustNode (rot (*0.9)) n
+
+-- anyRule = anyOf [ eliminate, ruleI, ruleK0, ruleK1, ruleS0, ruleS1, ruleS2, ruleE0, ruleE1, ruleE2, ruleD0, ruleD1, ruleD2]
+
+-- The menu that appears at the top-left corner of the window allows it to select specific rules that we defined in Rules.hs. It comes in form of a tree, where the parent of a subtree is the disjunction of the subtree's rules.
+ruleTree = Branch "All"
+	[Leaf "Eliminate" eliminate,
+	 Leaf "I" ruleI,
+	 Branch "K" [Leaf "K0" ruleK0, Leaf "K1" ruleK1],
+	 Branch "S" [Leaf "S0" ruleS0, Leaf "S1" ruleS1, Leaf "S2" ruleS2],
+	 Branch "E" [Leaf "E0" ruleE0, Leaf "E1" ruleE1, Leaf "E2" ruleE2],
+	 Branch "D" [Leaf "D0" ruleD0, Leaf "D1" ruleD1, Leaf "D2" ruleD2]]
+
+-- That's it. I hope the tutorial was helpful. If you need any help in defining your own rewriting system, feel free to ask me (jan@rochel.info). Corrections / extensions of the library or the example applications are always welcome. Have fun with it!
+--
+-- Jan
diff --git a/Rules.hs b/Rules.hs
new file mode 100644
--- /dev/null
+++ b/Rules.hs
@@ -0,0 +1,109 @@
+-- Now we are going to specify rewriting rules that implement the SKI combinator reductions. Refer to the module decumentation of GraphRewriting.Pattern and GraphRewriting.Rule for more detailed information about the functions used here.
+module Rules where
+
+import Prelude.Unicode
+import Graph
+import GraphRewriting
+import GraphRewriting.Pattern.InteractionNet -- This gives us the 'activePair' pattern.
+
+
+-- The simplest of the SKI combinators is the I combinator. We match on the active pair of an I node and an applicator using the 'activePair' function which resides in the 'Pattern' monad. Note that the :-: is an infix constructor and is just an alternative representation of a pair. With the left-hand side of the rule given, we build a rule out of it that erases the matched nodes and connects the edges at the input port and the right output port of the applicator using the 'rewire' function.
+ruleI ∷ (View [Port] n, View SKI n) ⇒ Rule n
+ruleI = do
+	I {} :-: A {inp = iA, out2 = o2} ← activePair
+	rewire [[iA,o2]]
+
+-- The K0 node represents a K combinator that has not yet accumulated an argument, which is what this rule does. Again, we match an active pair of a K0 node and an applicator. Then we replace these nodes by a K1 node that has the right-hand subgraph of the applicator as an argument (at its output port).
+ruleK0 ∷ (View [Port] n, View SKI n) ⇒ Rule n
+ruleK0 = do
+	K0 {} :-: A  {inp = iA, out2 = o2} ← activePair
+	replace0 [Node $ K1 {inp = iA, out = o2}]
+
+-- The 'replace*' functions can be used replace the matched nodes by a combination of new nodes and rewirings, hence the constructors 'Wire' and 'Node'.
+ruleK1 ∷ (View [Port] n, View SKI n) ⇒ Rule n
+ruleK1 = do
+	K1 {out = oK} :-: A {inp = iA, out2 = o2A} ← activePair
+	replace0 [Wire iA oK, Node $ E {inp = o2A}]
+
+ruleS0 ∷ (View [Port] n, View SKI n) ⇒ Rule n
+ruleS0 = do
+	S0 {} :-: A {inp = iA, out2 = o2A} ← activePair
+	replace0 [Node $ S1 {inp = iA, out = o2A}]
+
+ruleS1 ∷ (View [Port] n, View SKI n) ⇒ Rule n
+ruleS1 = do
+	S1 {out = oS} :-: A {inp = iA, out2 = o2A} ← activePair
+	replace0 [Node $ S2 {inp = iA, out1 = oS, out2 = o2A}]
+
+-- If we need new edges for the right-hand side of the rewrite rule you can use 'replaceN' with N > 0.
+ruleS2 ∷ (View [Port] n, View SKI n) ⇒ Rule n
+ruleS2 = do
+	S2 {inp = iS, out1 = oS1, out2 = o2S} :-: a@A {out1 = o1A, out2 = o2A} ← activePair
+	replace3 $ \i1D iB i2D →
+		[Node $ A {inp = iS, out1 = oS1, out2 = i1D},
+		 Node $ a {out2 = iB},
+		 Node $ D {inp1 = i1D, inp2 = i2D, out = o2A},
+		 Node $ A {inp = iB, out1 = o2S, out2 = i2D}]
+
+-- This is an abstraction to match any active pair that involves a node with arity 0.
+arity0 ∷ (View [Port] n, View SKI n) ⇒ Pattern n (Pair SKI)
+arity0 = anyOf [e,i,k,s] where
+	e = do {pair@(n :-: E  {}) ← activePair; return pair}
+	i = do {pair@(n :-: I  {}) ← activePair; return pair}
+	k = do {pair@(n :-: K0 {}) ← activePair; return pair}
+	s = do {pair@(n :-: S0 {}) ← activePair; return pair}
+
+arity1 ∷ (View [Port] n, View SKI n) ⇒ Pattern n (Pair SKI)
+arity1 = k <|> s where
+	k = do {pair@(n :-: K1 {}) ← activePair; return pair}
+	s = do {pair@(n :-: S1 {}) ← activePair; return pair}
+
+-- If the left-hand side is to be erased completely without any rewirings or new nodes to be replaced with, use the 'erase'.
+ruleE0 ∷ (View [Port] n, View SKI n) ⇒ Rule n 
+ruleE0 = do
+	E {inp = iE} :-: n ← arity0
+	erase
+	
+ruleE1 ∷ (View [Port] n, View SKI n) ⇒ Rule n 
+ruleE1 = do
+	E {} :-: n ← arity1
+	replace0 [Node $ E {inp = out n}]
+	
+ruleE2 ∷ (View [Port] n, View SKI n) ⇒ Rule n 
+ruleE2 = do
+	E {inp = iE} :-: S2 {inp = iS, out1 = o1, out2 = o2} ← activePair
+	replace0 [Node $ E {inp = o1}, Node E {inp = o2}]
+
+ruleD0  ∷ (View [Port] n, View SKI n) ⇒ Rule n
+ruleD0 = do
+	D {inp1 = iD1, inp2 = iD2, out = oD} :-: n ← arity0
+	replace0 [Node $ n {inp = iD1}, Node $ n {inp = iD2}]
+
+ruleD1  ∷ (View [Port] n, View SKI n) ⇒ Rule n
+ruleD1 = do
+	D {inp1 = iD1, inp2 = iD2, out = oD} :-: n ← arity1
+	replace2 $ \iD1' iD2' →
+		[Node $ n {inp = iD1, out = iD1'},
+		 Node $ n {inp = iD2, out = iD2'},
+		 Node $ D {inp1 = iD1', inp2 = iD2', out = out n}]
+	
+ruleD2 ∷ (View [Port] n, View SKI n) ⇒ Rule n 
+ruleD2 = do
+	D {inp1 = iD1, inp2 = iD2, out = oD} :-: S2 {inp = iS, out1 = o1, out2 = o2} ← activePair
+	replace4 $ \l1 l2 x1 x2 →
+		[Node $ S2 {inp = iD1, out1 = l1, out2 = x1},
+		 Node $ S2 {inp = iD2, out1 = x2, out2 = l2},
+		 Node $ D {inp1 = l1, inp2 = x2, out = o1},
+		 Node $ D {inp1 = x1, inp2 = l2, out = o2}]
+
+-- Here is the only rule that is not an interaction-net reduction, hence it does not rely on the 'activePair' pattern. First we match on an eraser node anywhere in the graph. Next we require a duplicator node that is connected to the eraser. Therefore we use the 'previous' pattern that returns a reference to the previously matched node and feed it to the 'neighbour' function that matches on nodes connected to the referenced node.
+eliminate ∷ (View [Port] n, View SKI n) ⇒ Rule n
+eliminate = do
+	E {inp = iE} ← node
+	D {out = oD, inp1 = i1, inp2 = i2} ← neighbour =<< previous
+	require (iE ≡ i1 ∨ iE ≡ i2)
+	if iE ≡ i1
+		then rewire [[oD,i2]]
+		else rewire [[oD,i1]]
+
+-- Together with the signature defined in Graph.hs these rules can be used to actually perform graph transformations. Therefore use the 'GraphRewriting.Graph.execGraph' and 'GraphRewriting.Rule.apply' functions. In GL.hs we're going to provide the layouting and graphics engines information on how to arrange and display the nodes.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/Term.hs b/Term.hs
new file mode 100644
--- /dev/null
+++ b/Term.hs
@@ -0,0 +1,43 @@
+-- | Syntax does not allow spaces, combinator names in capital letters, any other character besides combinators and parentheses is interpreted as a variable name. 
+
+module Term where
+
+import Prelude.Unicode
+import Text.ParserCombinators.Parsec as Parsec
+import Control.Monad (liftM)
+
+data Expr = A Expr Expr | S | K | I | {- B | C | -} V String deriving (Ord, Eq)
+
+instance Show Expr where
+	show = showComp False where
+		showComp :: Bool → Expr → String
+		showComp isComponent expr = case expr of
+			A (A e1 e2) e3 → maybeWrap $ show e1 ⧺ " " ⧺ diverge e2 ⧺ " " ⧺ diverge e3
+			A e1 e2 → maybeWrap $ diverge e1 ⧺ " " ⧺ diverge e2
+			S → "S"
+			K → "K"
+			I → "I"
+--			B → "B"
+--			C → "C"
+			V v → v
+			where
+			maybeWrap str = if isComponent then "(" ⧺ str ⧺ ")" else str
+			diverge = showComp True
+
+parse ∷ String → Expr
+parse str = either (error ∘ show) id (Parsec.parse parser "(null)" str)
+
+parseFile ∷ FilePath → IO Expr
+parseFile = liftM (either (error ∘ show) id) ∘ Parsec.parseFromFile parser
+
+parser ∷ Parser Expr
+parser = let expr = parser in liftM (foldl1 A) $ many1 $ choice
+	[char 'S' >> return S,
+	 char 'K' >> return K,
+	 char 'I' >> return I,
+--	 char 'B' >> return B,
+--	 char 'C' >> return C,
+--	 char 'R' >> return B,
+--	 char 'L' >> return C,
+	 between (char '(') (char ')') expr,
+	 liftM (V ∘ return) letter]
diff --git a/graph-rewriting-ski.cabal b/graph-rewriting-ski.cabal
new file mode 100644
--- /dev/null
+++ b/graph-rewriting-ski.cabal
@@ -0,0 +1,35 @@
+Name:           graph-rewriting-ski
+Version:        0.4.3
+Copyright:      (c) 2010, Jan Rochel
+License:        BSD3
+License-File:   LICENSE
+Author:         Jan Rochel
+Maintainer:     jan@rochel.info
+Stability:      beta
+Build-Type:     Simple
+Synopsis:       Implementation of the SKI combinators as an interactive graph rewriting system
+Description:
+	This package serves as an example for how to use the graph-rewriting, graph-rewriting-layout, and graph-rewriting-gl packages to create a graph rewriting system with an interactive, graphical front-end. The sources are well documented and can used as a tutorial for implementing your own rewrite system. Start reading in Graph.hs
+Category:       Graphs, Application
+Extensions:     UnicodeSyntax
+Cabal-Version:  >= 1.6
+Build-Depends:
+	base >= 4 && < 4.3,
+	base-unicode-symbols >= 0.2 && < 0.3,
+	graph-rewriting >= 0.4.4 && < 0.5,
+	graph-rewriting-layout >= 0.4.1 && < 0.5,
+	graph-rewriting-gl >= 0.5 && < 0.6,
+	parsec >= 2.1 && < 2.2,
+	GLUT >= 2.2 && < 2.3,
+	OpenGL >= 2.4 && < 2.5,
+	IndentParser >= 0.2 && < 0.3
+
+Executable:     ski
+Main-Is:        Main.hs
+Extensions:
+	UnicodeSyntax
+	FlexibleInstances
+	FlexibleContexts
+	MultiParamTypeClasses
+GHC-Options:    -fno-warn-duplicate-exports -fwarn-unused-imports
+Other-Modules:  GL Graph Rules Term
