diff --git a/core/alien/c-types/c-types-docs.factor b/core/alien/c-types/c-types-docs.factor
index f6418295f7..f4aa297a3a 100755
--- a/core/alien/c-types/c-types-docs.factor
+++ b/core/alien/c-types/c-types-docs.factor
@@ -34,6 +34,10 @@ HELP: stack-size
 { $description "Outputs the number of bytes to reserve on the C stack by a value of this C type. In most cases this is equal to " { $link heap-size } ", except on some platforms where C structs are passed by invisible reference, in which case a C struct type only uses as much space as a pointer on the C stack." }
 { $errors "Throws a " { $link no-c-type } " error if the type does not exist." } ;
 
+HELP: byte-length
+{ $values { "seq" "A byte array or float array" } { "n" "a non-negative integer" } }
+{ $contract "Outputs the size of the byte array or float array data in bytes as presented to the C library interface." } ;
+
 HELP: c-getter
 { $values { "name" string } { "quot" "a quotation with stack effect " { $snippet "( c-ptr n -- obj )" } } }
 { $description "Outputs a quotation which reads values of this C type from a C structure." }
diff --git a/core/alien/c-types/c-types.factor b/core/alien/c-types/c-types.factor
index 88df823e5b..1f0f6b121e 100755
--- a/core/alien/c-types/c-types.factor
+++ b/core/alien/c-types/c-types.factor
@@ -1,9 +1,10 @@
-! Copyright (C) 2004, 2007 Slava Pestov.
+! Copyright (C) 2004, 2008 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
-USING: byte-arrays arrays generator.registers assocs
-kernel kernel.private libc math namespaces parser sequences
-strings words assocs splitting math.parser cpu.architecture
-alien alien.accessors quotations system compiler.units ;
+USING: bit-arrays byte-arrays float-arrays arrays
+generator.registers assocs kernel kernel.private libc math
+namespaces parser sequences strings words assocs splitting
+math.parser cpu.architecture alien alien.accessors quotations
+system compiler.units ;
 IN: alien.c-types
 
 TUPLE: c-type
@@ -107,6 +108,14 @@ M: string stack-size c-type stack-size ;
 
 M: c-type stack-size c-type-size ;
 
+GENERIC: byte-length ( seq -- n ) flushable
+
+M: bit-array byte-length length 7 + -3 shift ;
+
+M: byte-array byte-length length ;
+
+M: float-array byte-length length "double" heap-size * ;
+
 : c-getter ( name -- quot )
     c-type c-type-getter [
         [ "Cannot read struct fields with type" throw ]
diff --git a/core/float-arrays/float-arrays-docs.factor b/core/float-arrays/float-arrays-docs.factor
index 70bbfe296f..cb36aade6b 100644
--- a/core/float-arrays/float-arrays-docs.factor
+++ b/core/float-arrays/float-arrays-docs.factor
@@ -32,7 +32,7 @@ HELP: <float-array> ( n initial -- float-array )
 
 HELP: >float-array
 { $values { "seq" "a sequence" } { "float-array" float-array } }
-{ $description "Outputs a freshly-allocated float array whose elements have the same boolean values as a given sequence." }
+{ $description "Outputs a freshly-allocated float array whose elements have the same floating-point values as a given sequence." }
 { $errors "Throws an error if the sequence contains elements other than real numbers." } ;
 
 HELP: 1float-array
diff --git a/extra/assocs/lib/lib.factor b/extra/assocs/lib/lib.factor
index 849f88023f..182f04a367 100755
--- a/extra/assocs/lib/lib.factor
+++ b/extra/assocs/lib/lib.factor
@@ -1,9 +1,6 @@
-USING: assocs kernel vectors sequences ;
+USING: assocs kernel vectors sequences namespaces ;
 IN: assocs.lib
 
-: insert-at ( value key assoc -- )
-    [ ?push ] change-at ;
-
 : >set ( seq -- hash )
     [ dup ] H{ } map>assoc ;
 
@@ -19,5 +16,19 @@ IN: assocs.lib
 : at-default ( key assoc -- value/key )
     dupd at [ nip ] when* ;
 
-: at-peek ( key assoc -- value ? )
-    at* dup >r [ peek ] when r> ;
+: insert-at ( value key assoc -- )
+    [ ?push ] change-at ;
+
+: peek-at* ( key assoc -- obj ? )
+    at* dup [ >r peek r> ] when ;
+
+: peek-at ( key assoc -- obj )
+    peek-at* drop ;
+
+: >multi-assoc ( assoc -- new-assoc )
+    [ 1vector ] assoc-map ;
+
+: multi-assoc-each ( assoc quot -- )
+    [ with each ] curry assoc-each ; inline
+
+: insert ( value variable -- ) namespace insert-at ;
diff --git a/extra/automata/automata.factor b/extra/automata/automata.factor
index 732033fb75..cd799d477e 100644
--- a/extra/automata/automata.factor
+++ b/extra/automata/automata.factor
@@ -1,6 +1,6 @@
 
 USING: kernel math math.parser random arrays hashtables assocs sequences
-       vars strings.lib ;
+       vars ;
 
 IN: automata
 
@@ -108,4 +108,4 @@ last-line> height> [ drop step-capped-line dup ] map >bitmap >last-line ;
 
 ! : start-loop ( -- ) t >loop-flag [ loop ] in-thread ;
 
-! : stop-loop ( -- ) f >loop-flag ;
\ No newline at end of file
+! : stop-loop ( -- ) f >loop-flag ;
diff --git a/extra/bunny/bunny.factor b/extra/bunny/bunny.factor
index 550eb50e0a..38f8e32fb6 100755
--- a/extra/bunny/bunny.factor
+++ b/extra/bunny/bunny.factor
@@ -1,112 +1,69 @@
-! From http://www.ffconsultancy.com/ocaml/bunny/index.html
 USING: alien alien.c-types arrays sequences math
 math.vectors math.matrices math.parser io io.files kernel opengl
 opengl.gl opengl.glu shuffle http.client vectors timers
 namespaces ui.gadgets ui.gadgets.canvas ui.render ui splitting
-combinators tools.time system combinators.lib ;
+combinators tools.time system combinators.lib combinators.cleave
+float-arrays continuations opengl.demo-support multiline
+ui.gestures
+bunny.fixed-pipeline bunny.cel-shaded bunny.outlined bunny.model ;
 IN: bunny
 
-: numbers ( str -- seq )
-    " " split [ string>number ] map [ ] subset ;
+TUPLE: bunny-gadget model geom draw-seq draw-n ;
 
-: (parse-model) ( vs is -- vs is )
-    readln [
-        numbers {
-            { [ dup length 5 = ] [ 3 head pick push ] }
-            { [ dup first 3 = ] [ 1 tail over push ] }
-            { [ t ] [ drop ] }
-        } cond (parse-model)
-    ] when* ;
+: <bunny-gadget> ( -- bunny-gadget )
+    0.0 0.0 0.375 <demo-gadget>
+    maybe-download read-model {
+        set-delegate
+        set-bunny-gadget-model
+    } bunny-gadget construct ;
 
-: parse-model ( stream -- vs is )
-    [
-        100000 <vector> 100000 <vector> (parse-model)
-    ] with-stream
-    [
-        over length # " vertices, " %
-        dup length # " triangles" %
-    ] "" make print ;
+: bunny-gadget-draw ( gadget -- draw )
+    { bunny-gadget-draw-n bunny-gadget-draw-seq }
+    get-slots nth ;
 
-: n ( vs triple -- n )
-    swap [ nth ] curry map
-    dup third over first v- >r dup second swap first v- r> cross
-    vneg normalize ;
+: bunny-gadget-next-draw ( gadget -- )
+    dup { bunny-gadget-draw-seq bunny-gadget-draw-n }
+    get-slots
+    1+ swap length mod
+    swap [ set-bunny-gadget-draw-n ] keep relayout-1 ;
 
-: normal ( ns vs triple -- )
-    [ n ] keep [ rot [ v+ ] change-nth ] each-with2 ;
-
-: normals ( vs is -- ns )
-    over length { 0.0 0.0 0.0 } <array> -rot
-    [ >r 2dup r> normal ] each drop
-    [ normalize ] map ;
-
-: read-model ( stream -- model )
-    "Reading model" print flush [
-        <file-reader> parse-model [ normals ] 2keep 3array
-    ] time ;
-
-: model-path "bun_zipper.ply" ;
-
-: model-url "http://factorcode.org/bun_zipper.ply" ;
-
-: maybe-download ( -- path )
-    model-path resource-path dup exists? [
-        "Downloading bunny from " write
-        model-url dup print flush
-        over download-to
-    ] unless ;
-
-: draw-triangle ( ns vs triple -- )
-    [ dup roll nth gl-normal swap nth gl-vertex ] each-with2 ;
-
-: draw-bunny ( ns vs is -- )
-    GL_TRIANGLES [ [ draw-triangle ] each-with2 ] do-state ;
-
-TUPLE: bunny-gadget model ;
-
-: <bunny-gadget> ( model -- gadget )
-    <canvas>
-    { set-bunny-gadget-model set-delegate }
-    bunny-gadget construct ;
-
-M: bunny-gadget graft* 10 10 add-timer ;
-
-M: bunny-gadget ungraft* dup delegate ungraft* remove-timer ;
-
-M: bunny-gadget tick relayout-1 ;
-
-: aspect ( gadget -- x ) rect-dim first2 /f ;
-
-M: bunny-gadget draw-gadget*
+M: bunny-gadget graft* ( gadget -- )
     GL_DEPTH_TEST glEnable
-    GL_SCISSOR_TEST glDisable
-    1.0 glClearDepth
-    0.0 0.0 0.0 1.0 glClearColor
-    GL_DEPTH_BUFFER_BIT GL_COLOR_BUFFER_BIT bitor glClear
-    GL_PROJECTION glMatrixMode
-    glLoadIdentity
-    45.0 over aspect 0.1 1.0 gluPerspective
-    0.0 0.12 -0.25  0.0 0.1 0.0  0.0 1.0 0.0 gluLookAt
-    GL_MODELVIEW glMatrixMode
-    glLoadIdentity
-    GL_LEQUAL glDepthFunc
-    GL_LIGHTING glEnable
-    GL_LIGHT0 glEnable
-    GL_COLOR_MATERIAL glEnable
-    GL_LIGHT0 GL_POSITION { 1.0 -1.0 1.0 1.0 } >c-float-array glLightfv
-    millis 24000 mod 0.015 * 0.0 1.0 0.0 glRotated
-    GL_FRONT_AND_BACK GL_SHININESS 100.0 glMaterialf
-    GL_FRONT_AND_BACK GL_SPECULAR glColorMaterial
-    GL_FRONT_AND_BACK GL_AMBIENT_AND_DIFFUSE glColorMaterial
-    0.6 0.5 0.5 1.0 glColor4d
-    [ bunny-gadget-model first3 draw-bunny ] draw-canvas ;
+    dup bunny-gadget-model <bunny-geom>
+    over {
+        [ <bunny-fixed-pipeline> ]
+        [ <bunny-cel-shaded> ]
+        [ <bunny-outlined> ]
+    } map-call-with [ ] subset
+    0
+    roll {
+        set-bunny-gadget-geom
+        set-bunny-gadget-draw-seq
+        set-bunny-gadget-draw-n
+    } set-slots ;
 
-M: bunny-gadget pref-dim* drop { 400 300 } ;
+M: bunny-gadget ungraft* ( gadget -- )
+    { bunny-gadget-geom bunny-gadget-draw-seq } get-slots
+    [ [ dispose ] when* ] each
+    [ dispose ] when* ;
+
+M: bunny-gadget draw-gadget* ( gadget -- )
+    0.15 0.15 0.15 1.0 glClearColor
+    GL_DEPTH_BUFFER_BIT GL_COLOR_BUFFER_BIT bitor glClear
+    dup demo-gadget-set-matrices
+    GL_MODELVIEW glMatrixMode
+    0.0 -0.12 0.0 glTranslatef
+    { bunny-gadget-geom bunny-gadget-draw } get-slots
+    draw-bunny ;
+
+M: bunny-gadget pref-dim* ( gadget -- dim )
+    drop { 640 480 } ;
+    
+bunny-gadget H{
+    { T{ key-down f f "TAB" } [ bunny-gadget-next-draw ] }
+} set-gestures
 
 : bunny-window ( -- )
-    [
-        maybe-download read-model <bunny-gadget>
-        "Bunny" open-window
-    ] with-ui ;
+    [ <bunny-gadget> "Bunny" open-window ] with-ui ;
 
 MAIN: bunny-window
diff --git a/extra/bunny/cel-shaded/cel-shaded.factor b/extra/bunny/cel-shaded/cel-shaded.factor
new file mode 100644
index 0000000000..fc42ca971e
--- /dev/null
+++ b/extra/bunny/cel-shaded/cel-shaded.factor
@@ -0,0 +1,92 @@
+USING: arrays bunny.model combinators.lib continuations
+kernel multiline opengl opengl.gl sequences ;
+IN: bunny.cel-shaded
+
+STRING: vertex-shader-source
+varying vec3 position, normal, viewer;
+
+void
+main()
+{
+    gl_Position = ftransform();
+
+    position = gl_Vertex.xyz;
+    normal = gl_Normal;
+    viewer = vec3(0, 0, 1) * gl_NormalMatrix;
+}
+
+;
+
+STRING: cel-shaded-fragment-shader-lib-source
+varying vec3 position, normal, viewer;
+uniform vec3 light_direction;
+uniform vec4 color;
+uniform vec4 ambient, diffuse;
+uniform float shininess;
+
+float
+modulate(vec3 direction, vec3 normal)
+{
+    return dot(direction, normal) * 0.5 + 0.5;
+}
+
+float
+cel(float m)
+{
+    return smoothstep(0.25, 0.255, m) * 0.4 + smoothstep(0.695, 0.70, m) * 0.5;
+}
+
+vec4
+cel_light()
+{
+    vec3 direction = normalize(light_direction - position);
+    vec3 reflection = reflect(direction, normal);
+    vec4 ad = (ambient + diffuse * vec4(vec3(cel(modulate(direction, normal))), 1));
+    float s = cel(pow(max(dot(-reflection, viewer), 0.0), shininess));
+    return ad * color + vec4(vec3(s), 0);
+}
+
+;
+
+STRING: cel-shaded-fragment-shader-main-source
+vec4 cel_light();
+
+void
+main()
+{
+    gl_FragColor = cel_light();
+}
+
+;
+
+TUPLE: bunny-cel-shaded program ;
+
+: cel-shading-supported? ( -- ? )
+    "2.0" { "GL_ARB_shader_objects" }
+    has-gl-version-or-extensions? ;
+
+: <bunny-cel-shaded> ( gadget -- draw )
+    drop
+    cel-shading-supported? [
+        vertex-shader-source <vertex-shader> check-gl-shader
+        cel-shaded-fragment-shader-lib-source <fragment-shader> check-gl-shader
+        cel-shaded-fragment-shader-main-source <fragment-shader> check-gl-shader
+        3array <gl-program> check-gl-program
+        { set-bunny-cel-shaded-program } bunny-cel-shaded construct
+    ] [ f ] if ;
+
+: (draw-cel-shaded-bunny) ( geom program -- )
+    {
+        { "light_direction" [ 1.0 -1.0 1.0 glUniform3f ] }
+        { "color"           [ 0.6 0.5 0.5 1.0 glUniform4f ] }
+        { "ambient"         [ 0.2 0.2 0.2 0.2 glUniform4f ] }
+        { "diffuse"         [ 0.8 0.8 0.8 0.8 glUniform4f ] }
+        { "shininess"       [ 100.0 glUniform1f ] }
+    } [ bunny-geom ] with-gl-program ;
+
+M: bunny-cel-shaded draw-bunny
+    bunny-cel-shaded-program (draw-cel-shaded-bunny) ;
+
+M: bunny-cel-shaded dispose
+    bunny-cel-shaded-program delete-gl-program ;
+
diff --git a/extra/bunny/fixed-pipeline/fixed-pipeline.factor b/extra/bunny/fixed-pipeline/fixed-pipeline.factor
new file mode 100644
index 0000000000..f3fb68e515
--- /dev/null
+++ b/extra/bunny/fixed-pipeline/fixed-pipeline.factor
@@ -0,0 +1,25 @@
+USING: alien.c-types continuations kernel
+opengl opengl.gl bunny.model ;
+IN: bunny.fixed-pipeline
+
+TUPLE: bunny-fixed-pipeline ;
+
+: <bunny-fixed-pipeline> ( gadget -- draw )
+    drop
+    { } bunny-fixed-pipeline construct ;
+
+M: bunny-fixed-pipeline draw-bunny
+    drop
+    GL_LIGHTING glEnable
+    GL_LIGHT0 glEnable
+    GL_COLOR_MATERIAL glEnable
+    GL_LIGHT0 GL_POSITION { 1.0 -1.0 1.0 1.0 } >c-float-array glLightfv
+    GL_FRONT_AND_BACK GL_SHININESS 100.0 glMaterialf
+    GL_FRONT_AND_BACK GL_SPECULAR glColorMaterial
+    GL_FRONT_AND_BACK GL_AMBIENT_AND_DIFFUSE glColorMaterial
+    0.6 0.5 0.5 1.0 glColor4f
+    bunny-geom ;
+
+M: bunny-fixed-pipeline dispose
+    drop ;
+
diff --git a/extra/bunny/model/model.factor b/extra/bunny/model/model.factor
new file mode 100644
index 0000000000..e3df6bb26c
--- /dev/null
+++ b/extra/bunny/model/model.factor
@@ -0,0 +1,113 @@
+USING: alien alien.c-types arrays sequences math
+math.vectors math.matrices math.parser io io.files kernel opengl
+opengl.gl opengl.glu shuffle http.client vectors splitting
+tools.time system combinators combinators.lib combinators.cleave
+float-arrays continuations namespaces ;
+IN: bunny.model
+
+: numbers ( str -- seq )
+    " " split [ string>number ] map [ ] subset ;
+
+: (parse-model) ( vs is -- vs is )
+    readln [
+        numbers {
+            { [ dup length 5 = ] [ 3 head pick push ] }
+            { [ dup first 3 = ] [ 1 tail over push ] }
+            { [ t ] [ drop ] }
+        } cond (parse-model)
+    ] when* ;
+
+: parse-model ( stream -- vs is )
+    [
+        100000 <vector> 100000 <vector> (parse-model)
+    ] with-stream
+    [
+        over length # " vertices, " %
+        dup length # " triangles" %
+    ] "" make print ;
+
+: n ( vs triple -- n )
+    swap [ nth ] curry map
+    dup third over first v- >r dup second swap first v- r> cross
+    vneg normalize ;
+
+: normal ( ns vs triple -- )
+    [ n ] keep [ rot [ v+ ] change-nth ] each-with2 ;
+
+: normals ( vs is -- ns )
+    over length { 0.0 0.0 0.0 } <array> -rot
+    [ >r 2dup r> normal ] each drop
+    [ normalize ] map ;
+
+: read-model ( stream -- model )
+    "Reading model" print flush [
+        <file-reader> parse-model [ normals ] 2keep 3array
+    ] time ;
+
+: model-path "bun_zipper.ply" ;
+
+: model-url "http://factorcode.org/bun_zipper.ply" ;
+
+: maybe-download ( -- path )
+    model-path resource-path dup exists? [
+        "Downloading bunny from " write
+        model-url dup print flush
+        over download-to
+    ] unless ;
+
+: (draw-triangle) ( ns vs triple -- )
+    [ dup roll nth gl-normal swap nth gl-vertex ] each-with2 ;
+
+: draw-triangles ( ns vs is -- )
+    GL_TRIANGLES [ [ (draw-triangle) ] each-with2 ] do-state ;
+
+TUPLE: bunny-dlist list ;
+TUPLE: bunny-buffers array element-array nv ni ;
+
+: <bunny-dlist> ( model -- geom )
+    GL_COMPILE [ first3 draw-triangles ] make-dlist
+    bunny-dlist construct-boa ;
+
+: <bunny-buffers> ( model -- geom )
+    [
+        [ first concat ] [ second concat ] bi
+        append >float-array
+        GL_ARRAY_BUFFER swap GL_STATIC_DRAW <gl-buffer>
+    ] [
+        third concat >c-uint-array
+        GL_ELEMENT_ARRAY_BUFFER swap GL_STATIC_DRAW <gl-buffer>
+    ]
+    [ first length 3 * ] [ third length 3 * ] tetra
+    bunny-buffers construct-boa ;
+
+GENERIC: bunny-geom ( geom -- )
+GENERIC: draw-bunny ( geom draw -- )
+
+M: bunny-dlist bunny-geom
+    bunny-dlist-list glCallList ;
+
+M: bunny-buffers bunny-geom
+    dup {
+        bunny-buffers-array
+        bunny-buffers-element-array
+    } get-slots [
+        GL_VERTEX_ARRAY GL_NORMAL_ARRAY 2array [
+            GL_DOUBLE 0 0 buffer-offset glNormalPointer
+            dup bunny-buffers-nv "double" heap-size * buffer-offset
+            3 GL_DOUBLE 0 roll glVertexPointer
+            bunny-buffers-ni
+            GL_TRIANGLES swap GL_UNSIGNED_INT 0 buffer-offset glDrawElements
+        ] all-enabled-client-state
+    ] with-array-element-buffers ;
+
+M: bunny-dlist dispose
+    bunny-dlist-list delete-dlist ;
+
+M: bunny-buffers dispose
+    { bunny-buffers-array bunny-buffers-element-array } get-slots
+    delete-gl-buffer delete-gl-buffer ;
+
+: <bunny-geom> ( model -- geom )
+    "1.5" { "GL_ARB_vertex_buffer_object" }
+    has-gl-version-or-extensions?
+    [ <bunny-buffers> ] [ <bunny-dlist> ] if ;
diff --git a/extra/bunny/outlined/outlined.factor b/extra/bunny/outlined/outlined.factor
new file mode 100644
index 0000000000..9de341561c
--- /dev/null
+++ b/extra/bunny/outlined/outlined.factor
@@ -0,0 +1,239 @@
+USING: arrays bunny.model bunny.cel-shaded
+combinators.lib continuations kernel math multiline
+opengl opengl.gl sequences ui.gadgets ;
+IN: bunny.outlined
+
+STRING: outlined-pass1-fragment-shader-main-source
+varying vec3 normal;
+vec4 cel_light();
+
+void
+main()
+{
+    gl_FragData[0] = cel_light();
+    gl_FragData[1] = vec4(normal, 1);
+}
+
+;
+
+STRING: outlined-pass2-vertex-shader-source
+varying vec2 coord;
+
+void
+main()
+{
+    gl_Position = ftransform();
+    coord = (gl_Vertex * vec4(0.5) + vec4(0.5)).xy;
+}
+
+;
+
+STRING: outlined-pass2-fragment-shader-source
+uniform sampler2D colormap, normalmap, depthmap;
+uniform vec4 line_color;
+varying vec2 coord;
+
+const float DEPTH_RATIO_THRESHOLD = 1.001, SAMPLE_SPREAD = 1.0/512.0;
+
+float
+depth_sample(vec2 c)
+{
+    return texture2D(depthmap, c).x;
+}
+bool
+are_depths_border(vec3 depths)
+{
+    return any(lessThan(depths, vec3(1.0/DEPTH_RATIO_THRESHOLD)))
+        || any(greaterThan(depths, vec3(DEPTH_RATIO_THRESHOLD)));
+}
+
+vec3
+normal_sample(vec2 c)
+{
+    return texture2D(normalmap, c).xyz;
+}
+
+float
+min6(float a, float b, float c, float d, float e, float f)
+{
+    return min(min(min(min(min(a, b), c), d), e), f);
+}
+
+float
+border_factor(vec2 c)
+{
+    vec2 coord1 = c + vec2(-SAMPLE_SPREAD, -SAMPLE_SPREAD),
+         coord2 = c + vec2( SAMPLE_SPREAD, -SAMPLE_SPREAD),
+         coord3 = c + vec2(-SAMPLE_SPREAD,  SAMPLE_SPREAD),
+         coord4 = c + vec2( SAMPLE_SPREAD,  SAMPLE_SPREAD);
+    
+    vec3 normal1 = normal_sample(coord1),
+         normal2 = normal_sample(coord2),
+         normal3 = normal_sample(coord3),
+         normal4 = normal_sample(coord4);
+
+    if (dot(normal1, normal1) < 0.5
+        && dot(normal2, normal2) < 0.5
+        && dot(normal3, normal3) < 0.5
+        && dot(normal4, normal4) < 0.5) {
+        return 0.0;
+    } else {
+        vec4 depths = vec4(depth_sample(coord1),
+                           depth_sample(coord2),
+                           depth_sample(coord3),
+                           depth_sample(coord4));
+    
+        vec3 ratios1 = depths.xxx/depths.yzw, ratios2 = depths.yyz/depths.zww;
+    
+        if (are_depths_border(ratios1) || are_depths_border(ratios2)) {
+            return 1.0;
+        } else {
+            float normal_border = 1.0 - min6(
+                dot(normal1, normal2),
+                dot(normal1, normal3),
+                dot(normal1, normal4),
+                dot(normal2, normal3),
+                dot(normal2, normal4),
+                dot(normal3, normal4)
+            );
+    
+            return normal_border;
+        }
+    }
+}
+
+void
+main()
+{
+    gl_FragColor = mix(texture2D(colormap, coord), line_color, border_factor(coord));
+}
+
+;
+
+TUPLE: bunny-outlined
+    gadget
+    pass1-program pass2-program
+    color-texture normal-texture depth-texture
+    framebuffer framebuffer-dim ;
+
+: outlining-supported? ( -- ? )
+    "2.0" {
+        "GL_ARB_shading_objects"
+        "GL_ARB_draw_buffers"
+        "GL_ARB_multitexture"
+    } has-gl-version-or-extensions? {
+        "GL_EXT_framebuffer_object"
+        "GL_ARB_texture_float"
+    } has-gl-extensions? and ;
+
+: pass1-program ( -- program )
+    vertex-shader-source <vertex-shader> check-gl-shader
+    cel-shaded-fragment-shader-lib-source <fragment-shader> check-gl-shader
+    outlined-pass1-fragment-shader-main-source <fragment-shader> check-gl-shader
+    3array <gl-program> check-gl-program ;
+
+: pass2-program ( -- program )
+    outlined-pass2-vertex-shader-source
+    outlined-pass2-fragment-shader-source <simple-gl-program> ;
+
+: <bunny-outlined> ( gadget -- draw )
+    outlining-supported? [
+        pass1-program pass2-program {
+            set-bunny-outlined-gadget
+            set-bunny-outlined-pass1-program
+            set-bunny-outlined-pass2-program
+        } bunny-outlined construct
+    ] [ drop f ] if ;
+
+: (framebuffer-texture) ( dim iformat xformat -- texture )
+    swapd >r >r >r
+    GL_TEXTURE0 glActiveTexture
+    gen-texture GL_TEXTURE_2D over glBindTexture
+    GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_CLAMP glTexParameteri
+    GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_CLAMP glTexParameteri
+    GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_NEAREST glTexParameteri
+    GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_NEAREST glTexParameteri
+    GL_TEXTURE_2D 0 r> r> first2 0 r> GL_UNSIGNED_BYTE f glTexImage2D ;
+
+: (attach-framebuffer-texture) ( texture attachment -- )
+    swap >r >r
+    GL_FRAMEBUFFER_EXT r> GL_TEXTURE_2D r> 0 glFramebufferTexture2DEXT
+    gl-error ;
+
+: (make-framebuffer) ( color-texture normal-texture depth-texture -- framebuffer )
+    3array gen-framebuffer dup [
+        swap GL_COLOR_ATTACHMENT0_EXT
+             GL_COLOR_ATTACHMENT1_EXT
+             GL_DEPTH_ATTACHMENT_EXT 3array [ (attach-framebuffer-texture) ] 2each
+        check-framebuffer
+    ] with-framebuffer ;
+
+: dispose-framebuffer ( draw -- )
+    dup bunny-outlined-framebuffer-dim [
+        {
+            [ bunny-outlined-framebuffer    [ delete-framebuffer ] when* ]
+            [ bunny-outlined-color-texture  [ delete-texture ] when* ]
+            [ bunny-outlined-normal-texture [ delete-texture ] when* ]
+            [ bunny-outlined-depth-texture  [ delete-texture ] when* ]
+            [ f swap set-bunny-outlined-framebuffer-dim ]
+        } call-with
+    ] [ drop ] if ;
+
+: remake-framebuffer-if-needed ( draw -- )
+    dup bunny-outlined-gadget rect-dim
+    over bunny-outlined-framebuffer-dim
+    over =
+    [ 2drop ]
+    [
+        swap dup dispose-framebuffer >r
+        dup GL_RGBA16F_ARB GL_RGBA (framebuffer-texture)
+        swap dup GL_RGBA16F_ARB GL_RGBA (framebuffer-texture)
+        swap dup GL_DEPTH_COMPONENT32 GL_DEPTH_COMPONENT (framebuffer-texture)
+        swap >r
+        [ (make-framebuffer) ] 3keep
+        r> r> {
+            set-bunny-outlined-framebuffer
+            set-bunny-outlined-color-texture
+            set-bunny-outlined-normal-texture
+            set-bunny-outlined-depth-texture
+            set-bunny-outlined-framebuffer-dim
+        } set-slots
+    ] if ;
+
+: clear-framebuffer ( -- )
+    GL_COLOR_ATTACHMENT0_EXT glDrawBuffer
+    0.15 0.15 0.15 1.0 glClearColor
+    GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT bitor glClear
+    GL_COLOR_ATTACHMENT1_EXT glDrawBuffer
+    0.0 0.0 0.0 0.0 glClearColor
+    GL_COLOR_BUFFER_BIT glClear ;
+
+: (pass1) ( geom draw -- )
+    dup bunny-outlined-framebuffer [
+        clear-framebuffer
+        { GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT1_EXT } set-draw-buffers
+        bunny-outlined-pass1-program (draw-cel-shaded-bunny)
+    ] with-framebuffer ;
+
+: (pass2) ( draw -- )
+    init-matrices
+    dup bunny-outlined-color-texture GL_TEXTURE_2D GL_TEXTURE0 bind-texture-unit
+    dup bunny-outlined-normal-texture GL_TEXTURE_2D GL_TEXTURE1 bind-texture-unit
+    dup bunny-outlined-depth-texture GL_TEXTURE_2D GL_TEXTURE2 bind-texture-unit
+    bunny-outlined-pass2-program {
+        { "colormap"   [ 0 glUniform1i ] }
+        { "normalmap"  [ 1 glUniform1i ] }
+        { "depthmap"   [ 2 glUniform1i ] }
+        { "line_color" [ 0.1 0.0 0.1 1.0 glUniform4f ] }
+    } [ { -1.0 -1.0 } { 1.0 1.0 } rect-vertices ] with-gl-program ;
+
+M: bunny-outlined draw-bunny
+    dup remake-framebuffer-if-needed
+    [ (pass1) ] keep (pass2) ;
+
+M: bunny-outlined dispose
+    {
+        [ bunny-outlined-pass1-program [ delete-gl-program ] when* ]
+        [ bunny-outlined-pass2-program [ delete-gl-program ] when* ]
+        [ dispose-framebuffer ]
+    } call-with ;
diff --git a/extra/cel-shading/cel-shading.factor b/extra/cel-shading/cel-shading.factor
deleted file mode 100644
index 64d23275e9..0000000000
--- a/extra/cel-shading/cel-shading.factor
+++ /dev/null
@@ -1,89 +0,0 @@
-USING: arrays bunny combinators.lib io io.files kernel
-       math math.functions multiline continuations debugger
-       opengl opengl.gl opengl-demo-support
-       sequences ui ui.gadgets ui.render ;
-IN: cel-shading
-
-TUPLE: cel-shading-gadget model program ;
-
-: <cel-shading-gadget> ( -- cel-shading-gadget )
-    0.0 0.0 0.375 <demo-gadget>
-    maybe-download read-model
-    { set-delegate set-cel-shading-gadget-model } cel-shading-gadget construct ;
-
-STRING: cel-shading-vertex-shader-source
-varying vec3 position, normal;
-
-void
-main()
-{
-    gl_Position = ftransform();
-    
-    position = gl_Vertex.xyz;
-    normal = gl_Normal;
-}
-
-;
-
-STRING: cel-shading-fragment-shader-source
-varying vec3 position, normal;
-uniform vec3 light_direction;
-uniform vec4 color;
-uniform vec4 ambient, diffuse;
-
-float
-smooth_modulate(vec3 direction, vec3 normal)
-{
-    return clamp(dot(direction, normal), 0.0, 1.0);
-}
-
-float
-modulate(vec3 direction, vec3 normal)
-{
-    float m = smooth_modulate(direction, normal);
-    return smoothstep(0.0, 0.01, m) * 0.4 + smoothstep(0.49, 0.5, m) * 0.5;
-}
-
-void
-main()
-{
-    vec3 direction = normalize(light_direction - position);
-    gl_FragColor = ambient + diffuse * color * vec4(vec3(modulate(direction, normal)), 1); 
-}
-
-;
-
-: cel-shading-program ( -- program )
-    cel-shading-vertex-shader-source cel-shading-fragment-shader-source
-    <simple-gl-program> ;
-
-M: cel-shading-gadget graft* ( gadget -- )
-    [ "2.0" { "GL_ARB_shader_objects" } require-gl-version-or-extensions
-    0.0 0.0 0.0 1.0 glClearColor
-    GL_CULL_FACE glEnable
-    GL_DEPTH_TEST glEnable
-    cel-shading-program swap set-cel-shading-gadget-program ] [ ] [ :c ] cleanup ;
-
-M: cel-shading-gadget ungraft* ( gadget -- )
-    cel-shading-gadget-program [ delete-gl-program ] when* ;
-
-: cel-shading-draw-setup ( gadget -- gadget )
-    [ demo-gadget-set-matrices ] keep
-    [ cel-shading-gadget-program
-        { [ "light_direction" glGetUniformLocation -25.0 45.0 80.0 glUniform3f ]
-          [ "color" glGetUniformLocation 0.6 0.5 0.5 1.0 glUniform4f ]
-          [ "ambient" glGetUniformLocation 0.2 0.2 0.2 0.2 glUniform4f ]
-          [ "diffuse" glGetUniformLocation 0.8 0.8 0.8 0.8 glUniform4f ] } call-with
-    ] keep ;
-
-M: cel-shading-gadget draw-gadget* ( gadget -- )
-    dup cel-shading-gadget-program [
-        cel-shading-draw-setup
-        0.0 -0.12 0.0 glTranslatef
-        cel-shading-gadget-model first3 draw-bunny
-    ] with-gl-program ;
-
-: cel-shading-window ( -- )
-    [ <cel-shading-gadget> "Cel Shading" open-window ] with-ui ;
-    
-MAIN: cel-shading-window
diff --git a/extra/cocoa/views/views.factor b/extra/cocoa/views/views.factor
index cc948df55f..7b8de9067c 100644
--- a/extra/cocoa/views/views.factor
+++ b/extra/cocoa/views/views.factor
@@ -1,7 +1,8 @@
 ! Copyright (C) 2006, 2007 Slava Pestov
 ! See http://factorcode.org/license.txt for BSD license.
 USING: alien.c-types arrays kernel math namespaces cocoa
-cocoa.messages cocoa.classes cocoa.types sequences ;
+cocoa.messages cocoa.classes cocoa.types sequences
+continuations ;
 IN: cocoa.views
 
 : NSOpenGLPFAAllRenderers 1 ;
@@ -35,11 +36,23 @@ IN: cocoa.views
 : NSOpenGLPFAPixelBuffer 90 ;
 : NSOpenGLPFAVirtualScreenCount 128 ;
 
+<PRIVATE
+
+SYMBOL: +software-renderer+
+
+PRIVATE>
+
+: with-software-renderer ( quot -- )
+    t +software-renderer+ set
+    [ f +software-renderer+ set ]
+    [ ] cleanup ; inline
+
 : <PixelFormat> ( -- pixelfmt )
     NSOpenGLPixelFormat -> alloc [
         NSOpenGLPFAWindow ,
         NSOpenGLPFADoubleBuffer ,
         NSOpenGLPFADepthSize , 16 ,
+        +software-renderer+ get [ NSOpenGLPFARobust , ] when
         0 ,
     ] { } make >c-int-array
     -> initWithAttributes:
diff --git a/extra/db/db.factor b/extra/db/db.factor
new file mode 100644
index 0000000000..b765924cd6
--- /dev/null
+++ b/extra/db/db.factor
@@ -0,0 +1,104 @@
+! Copyright (C) 2008 Doug Coleman.
+! See http://factorcode.org/license.txt for BSD license.
+USING: arrays assocs classes continuations kernel math
+namespaces sequences sequences.lib tuples words ;
+IN: db
+
+TUPLE: db handle ;
+C: <db> db ( handle -- obj )
+
+! HOOK: db-create db ( str -- )
+! HOOK: db-drop db ( str -- )
+GENERIC: db-open ( db -- )
+GENERIC: db-close ( db -- )
+
+TUPLE: statement sql params handle bound? ;
+
+TUPLE: simple-statement ;
+TUPLE: prepared-statement ;
+
+HOOK: <simple-statement> db ( str -- statement )
+HOOK: <prepared-statement> db ( str -- statement )
+
+GENERIC: prepare-statement ( statement -- )
+GENERIC: bind-statement* ( obj statement -- )
+GENERIC: rebind-statement ( obj statement -- )
+
+GENERIC: execute-statement ( statement -- )
+
+: bind-statement ( obj statement -- )
+    2dup dup statement-bound? [
+        rebind-statement
+    ] [
+        bind-statement*
+    ] if
+    tuck set-statement-params
+    t swap set-statement-bound? ;
+
+TUPLE: result-set sql params handle n max ;
+
+GENERIC: query-results ( query -- result-set )
+
+GENERIC: #rows ( result-set -- n )
+GENERIC: #columns ( result-set -- n )
+GENERIC# row-column 1 ( result-set n -- obj )
+GENERIC: advance-row ( result-set -- ? )
+
+: init-result-set ( result-set -- )
+    dup #rows over set-result-set-max
+    -1 swap set-result-set-n ;
+
+: <result-set> ( query handle tuple -- result-set )
+    >r >r { statement-sql statement-params } get-slots r>
+    {
+        set-result-set-sql
+        set-result-set-params
+        set-result-set-handle
+    } result-set construct r> construct-delegate ;
+
+: sql-row ( result-set -- seq )
+    dup #columns [ row-column ] with map ;
+
+: query-each ( statement quot -- )
+    over advance-row [
+        2drop
+    ] [
+        [ call ] 2keep query-each
+    ] if ; inline
+
+: query-map ( statement quot -- seq )
+    accumulator >r query-each r> { } like ; inline
+
+: with-db ( db quot -- )
+    [
+        over db-open
+        [ db swap with-variable ] curry with-disposal
+    ] with-scope ;
+
+: do-query ( query -- result-set )
+    query-results [ [ sql-row ] query-map ] with-disposal ;
+
+: do-bound-query ( obj query -- rows )
+    [ bind-statement ] keep do-query ;
+
+: do-bound-command ( obj query -- )
+    [ bind-statement ] keep execute-statement ;
+
+: sql-query ( sql -- rows )
+    <simple-statement> [ do-query ] with-disposal ;
+
+: sql-command ( sql -- )
+    <simple-statement> [ execute-statement ] with-disposal ;
+
+SYMBOL: in-transaction
+HOOK: begin-transaction db ( -- )
+HOOK: commit-transaction db ( -- )
+HOOK: rollback-transaction db ( -- )
+
+: in-transaction? ( -- ? ) in-transaction get ;
+
+: with-transaction ( quot -- )
+    t in-transaction [
+        begin-transaction
+        [ ] [ rollback-transaction ] cleanup commit-transaction
+    ] with-variable ;
diff --git a/extra/postgresql/authors.txt b/extra/db/postgresql/authors.txt
similarity index 100%
rename from extra/postgresql/authors.txt
rename to extra/db/postgresql/authors.txt
diff --git a/extra/postgresql/libpq/libpq.factor b/extra/db/postgresql/ffi/ffi.factor
similarity index 56%
rename from extra/postgresql/libpq/libpq.factor
rename to extra/db/postgresql/ffi/ffi.factor
index faeb3f9aa4..dbaa70c625 100644
--- a/extra/postgresql/libpq/libpq.factor
+++ b/extra/db/postgresql/ffi/ffi.factor
@@ -1,12 +1,10 @@
 ! Copyright (C) 2007 Doug Coleman.
 ! See http://factorcode.org/license.txt for BSD license.
-
 ! adapted from libpq-fe.h version 7.4.7
-! tested on debian linux with postgresql 7.4.7
-! Updated to 8.1
+! tested on debian linux with postgresql 8.1
 
 USING: alien alien.syntax combinators system ;
-IN: postgresql.libpq
+IN: db.postgresql.ffi
 
 <<
 "postgresql" {
@@ -17,45 +15,44 @@ IN: postgresql.libpq
 >>
 
 ! ConnSatusType
-: CONNECTION_OK                                         HEX: 0 ; inline
-: CONNECTION_BAD                                        HEX: 1 ; inline
-: CONNECTION_STARTED                            HEX: 2 ; inline
-: CONNECTION_MADE                                       HEX: 3 ; inline
-: CONNECTION_AWAITING_RESPONSE          HEX: 4 ; inline
-: CONNECTION_AUTH_OK                            HEX: 5 ; inline
-: CONNECTION_SETENV                                     HEX: 6 ; inline
-: CONNECTION_SSL_STARTUP                        HEX: 7 ; inline
-: CONNECTION_NEEDED                                     HEX: 8 ; inline
+: CONNECTION_OK                     HEX: 0 ; inline
+: CONNECTION_BAD                    HEX: 1 ; inline
+: CONNECTION_STARTED                HEX: 2 ; inline
+: CONNECTION_MADE                   HEX: 3 ; inline
+: CONNECTION_AWAITING_RESPONSE      HEX: 4 ; inline
+: CONNECTION_AUTH_OK                HEX: 5 ; inline
+: CONNECTION_SETENV                 HEX: 6 ; inline
+: CONNECTION_SSL_STARTUP            HEX: 7 ; inline
+: CONNECTION_NEEDED                 HEX: 8 ; inline
 
 ! PostgresPollingStatusType
-: PGRES_POLLING_FAILED                          HEX: 0 ; inline
-: PGRES_POLLING_READING                         HEX: 1 ; inline
-: PGRES_POLLING_WRITING                         HEX: 2 ; inline
-: PGRES_POLLING_OK                                      HEX: 3 ; inline
-: PGRES_POLLING_ACTIVE                          HEX: 4 ; inline
+: PGRES_POLLING_FAILED              HEX: 0 ; inline
+: PGRES_POLLING_READING             HEX: 1 ; inline
+: PGRES_POLLING_WRITING             HEX: 2 ; inline
+: PGRES_POLLING_OK                  HEX: 3 ; inline
+: PGRES_POLLING_ACTIVE              HEX: 4 ; inline
 
 ! ExecStatusType;
-: PGRES_EMPTY_QUERY                             HEX: 0 ; inline
-: PGRES_COMMAND_OK                                      HEX: 1 ; inline
-: PGRES_TUPLES_OK                                       HEX: 2 ; inline
-: PGRES_COPY_OUT                                        HEX: 3 ; inline
-: PGRES_COPY_IN                                         HEX: 4 ; inline
-: PGRES_BAD_RESPONSE                            HEX: 5 ; inline
-: PGRES_NONFATAL_ERROR                          HEX: 6 ; inline
-: PGRES_FATAL_ERROR                                     HEX: 7 ; inline
+: PGRES_EMPTY_QUERY                 HEX: 0 ; inline
+: PGRES_COMMAND_OK                  HEX: 1 ; inline
+: PGRES_TUPLES_OK                   HEX: 2 ; inline
+: PGRES_COPY_OUT                    HEX: 3 ; inline
+: PGRES_COPY_IN                     HEX: 4 ; inline
+: PGRES_BAD_RESPONSE                HEX: 5 ; inline
+: PGRES_NONFATAL_ERROR              HEX: 6 ; inline
+: PGRES_FATAL_ERROR                 HEX: 7 ; inline
 
 ! PGTransactionStatusType;
-: PQTRANS_IDLE                                          HEX: 0 ; inline
-: PQTRANS_ACTIVE                                        HEX: 1 ; inline
-: PQTRANS_INTRANS                                       HEX: 2 ; inline
-: PQTRANS_INERROR                                       HEX: 3 ; inline
-: PQTRANS_UNKNOWN                                       HEX: 4 ; inline
+: PQTRANS_IDLE                      HEX: 0 ; inline
+: PQTRANS_ACTIVE                    HEX: 1 ; inline
+: PQTRANS_INTRANS                   HEX: 2 ; inline
+: PQTRANS_INERROR                   HEX: 3 ; inline
+: PQTRANS_UNKNOWN                   HEX: 4 ; inline
 
 ! PGVerbosity;
-: PQERRORS_TERSE                                        HEX: 0 ; inline
-: PQERRORS_DEFAULT                                      HEX: 1 ; inline
-: PQERRORS_VERBOSE                                      HEX: 2 ; inline
-
+: PQERRORS_TERSE                    HEX: 0 ; inline
+: PQERRORS_DEFAULT                  HEX: 1 ; inline
+: PQERRORS_VERBOSE                  HEX: 2 ; inline
 
 TYPEDEF: int size_t
 TYPEDEF: int ConnStatusType
@@ -81,7 +78,6 @@ LIBRARY: postgresql
 
 
 ! Exported functions of libpq
-! ===   in fe-connect.c ===
 
 ! make a new client connection to the backend
 ! Asynchronous (non-blocking)
@@ -91,12 +87,12 @@ FUNCTION: PostgresPollingStatusType PQconnectPoll ( PGconn* conn ) ;
 ! Synchronous (blocking)
 FUNCTION: PGconn* PQconnectdb ( char* conninfo ) ;
 FUNCTION: PGconn* PQsetdbLogin ( char* pghost, char* pgport,
-                         char* pgoptions, char* pgtty,
-                         char* dbName,
-                         char* login, char* pwd ) ;
+             char* pgoptions, char* pgtty,
+             char* dbName,
+             char* login, char* pwd ) ;
 
 : PQsetdb ( M_PGHOST M_PGPORT M_PGOPT M_PGTTY M_DBNAME -- PGconn* )
-        f f PQsetdbLogin ;
+    f f PQsetdbLogin ;
 
 ! close the current connection and free the PGconn data structure
 FUNCTION: void PQfinish ( PGconn* conn ) ;
@@ -112,7 +108,7 @@ FUNCTION: void PQconninfoFree ( PQconninfoOption* connOptions ) ;
 ! parameters
 !
 ! Asynchronous (non-blocking)
-FUNCTION: int   PQresetStart ( PGconn* conn ) ;
+FUNCTION: int    PQresetStart ( PGconn* conn ) ;
 FUNCTION: PostgresPollingStatusType PQresetPoll ( PGconn* conn ) ;
 
 ! Synchronous (blocking)
@@ -125,7 +121,7 @@ FUNCTION: PGcancel* PQgetCancel ( PGconn* conn ) ;
 FUNCTION: void PQfreeCancel ( PGcancel* cancel ) ;
 
 ! issue a cancel request
-FUNCTION: int   PQrequestCancel ( PGconn* conn ) ;
+FUNCTION: int    PQrequestCancel ( PGconn* conn ) ;
 
 ! Accessor functions for PGconn objects
 FUNCTION: char* PQdb ( PGconn* conn ) ;
@@ -138,14 +134,14 @@ FUNCTION: char* PQoptions ( PGconn* conn ) ;
 FUNCTION: ConnStatusType PQstatus ( PGconn* conn ) ;
 FUNCTION: PGTransactionStatusType PQtransactionStatus ( PGconn* conn ) ;
 FUNCTION: char* PQparameterStatus ( PGconn* conn,
-                                  char* paramName ) ;
-FUNCTION: int   PQprotocolVersion ( PGconn* conn ) ;
-FUNCTION: int   PQServerVersion ( PGconn* conn ) ;
+                  char* paramName ) ;
+FUNCTION: int PQprotocolVersion ( PGconn* conn ) ;
+! FUNCTION: int PQServerVersion ( PGconn* conn ) ;
 FUNCTION: char* PQerrorMessage ( PGconn* conn ) ;
-FUNCTION: int   PQsocket ( PGconn* conn ) ;
-FUNCTION: int   PQbackendPID ( PGconn* conn ) ;
-FUNCTION: int   PQclientEncoding ( PGconn* conn ) ;
-FUNCTION: int   PQsetClientEncoding ( PGconn* conn, char* encoding ) ;
+FUNCTION: int PQsocket ( PGconn* conn ) ;
+FUNCTION: int PQbackendPID ( PGconn* conn ) ;
+FUNCTION: int PQclientEncoding ( PGconn* conn ) ;
+FUNCTION: int PQsetClientEncoding ( PGconn* conn, char* encoding ) ;
 
 ! May not be compiled into libpq
 ! Get the SSL structure associated with a connection
@@ -156,7 +152,7 @@ FUNCTION: void PQinitSSL ( int do_init ) ;
 
 ! Set verbosity for PQerrorMessage and PQresultErrorMessage
 FUNCTION: PGVerbosity PQsetErrorVerbosity ( PGconn* conn,
-        PGVerbosity verbosity ) ;
+    PGVerbosity verbosity ) ;
 
 ! Enable/disable tracing
 FUNCTION: void PQtrace ( PGconn* conn, FILE* debug_port ) ;
@@ -171,11 +167,11 @@ FUNCTION: void PQuntrace ( PGconn* conn ) ;
 
 ! Override default notice handling routines
 ! FUNCTION: PQnoticeReceiver PQsetNoticeReceiver ( PGconn* conn,
-                                        ! PQnoticeReceiver proc,
-                                        ! void* arg ) ;
+                    ! PQnoticeReceiver proc,
+                    ! void* arg ) ;
 ! FUNCTION: PQnoticeProcessor PQsetNoticeProcessor ( PGconn* conn,
-                                        ! PQnoticeProcessor proc,
-                                        ! void* arg ) ;
+                    ! PQnoticeProcessor proc,
+                    ! void* arg ) ;
 ! END BROKEN
 
 ! === in fe-exec.c ===
@@ -183,83 +179,83 @@ FUNCTION: void PQuntrace ( PGconn* conn ) ;
 ! Simple synchronous query
 FUNCTION: PGresult* PQexec ( PGconn* conn, char* query ) ;
 FUNCTION: PGresult* PQexecParams ( PGconn* conn,
-                         char* command,
-                         int nParams,
-                         Oid* paramTypes,
-                         char** paramValues,
-                         int* paramLengths,
-                         int* paramFormats,
-                         int resultFormat ) ;
+             char* command,
+             int nParams,
+             Oid* paramTypes,
+             char** paramValues,
+             int* paramLengths,
+             int* paramFormats,
+             int resultFormat ) ;
 FUNCTION: PGresult* PQprepare ( PGconn* conn, char* stmtName,
         char* query, int nParams,
         Oid* paramTypes ) ;
 FUNCTION: PGresult* PQexecPrepared ( PGconn* conn,
-                         char* stmtName,
-                         int nParams,
-                         char** paramValues,
-                         int* paramLengths,
-                         int* paramFormats,
-                         int resultFormat ) ;
+             char* stmtName,
+             int nParams,
+             char** paramValues,
+             int* paramLengths,
+             int* paramFormats,
+             int resultFormat ) ;
 
 ! Interface for multiple-result or asynchronous queries
 FUNCTION: int PQsendQuery ( PGconn* conn, char* query ) ;
 FUNCTION: int PQsendQueryParams ( PGconn* conn,
-                                  char* command,
-                                  int nParams,
-                                  Oid* paramTypes,
-                                  char** paramValues,
-                                  int* paramLengths,
-                                  int* paramFormats,
-                                  int resultFormat ) ;
+                  char* command,
+                  int nParams,
+                  Oid* paramTypes,
+                  char** paramValues,
+                  int* paramLengths,
+                  int* paramFormats,
+                  int resultFormat ) ;
 FUNCTION: PGresult* PQsendPrepare ( PGconn* conn, char* stmtName,
             char* query, int nParams,
             Oid* paramTypes ) ;
 FUNCTION: int PQsendQueryPrepared ( PGconn* conn,
-                                  char* stmtName,
-                                  int nParams,
-                                  char** paramValues,
-                                  int *paramLengths,
-                                  int *paramFormats,
-                                  int resultFormat ) ;
+                  char* stmtName,
+                  int nParams,
+                  char** paramValues,
+                  int *paramLengths,
+                  int *paramFormats,
+                  int resultFormat ) ;
 FUNCTION: PGresult* PQgetResult ( PGconn* conn ) ;
 
 ! Routines for managing an asynchronous query
-FUNCTION: int   PQisBusy ( PGconn* conn ) ;
-FUNCTION: int   PQconsumeInput ( PGconn* conn ) ;
+FUNCTION: int    PQisBusy ( PGconn* conn ) ;
+FUNCTION: int    PQconsumeInput ( PGconn* conn ) ;
 
 ! LISTEN/NOTIFY support
 FUNCTION: PGnotify* PQnotifies ( PGconn* conn ) ;
 
 ! Routines for copy in/out
-FUNCTION: int   PQputCopyData ( PGconn* conn, char* buffer, int nbytes ) ;
-FUNCTION: int   PQputCopyEnd ( PGconn* conn, char* errormsg ) ;
-FUNCTION: int   PQgetCopyData ( PGconn* conn, char** buffer, int async ) ;
+FUNCTION: int    PQputCopyData ( PGconn* conn, char* buffer, int nbytes ) ;
+FUNCTION: int    PQputCopyEnd ( PGconn* conn, char* errormsg ) ;
+FUNCTION: int    PQgetCopyData ( PGconn* conn, char** buffer, int async ) ;
 
 ! Deprecated routines for copy in/out
-FUNCTION: int   PQgetline ( PGconn* conn, char* string, int length ) ;
-FUNCTION: int   PQputline ( PGconn* conn, char* string ) ;
-FUNCTION: int   PQgetlineAsync ( PGconn* conn, char* buffer, int bufsize ) ;
-FUNCTION: int   PQputnbytes ( PGconn* conn, char* buffer, int nbytes ) ;
-FUNCTION: int   PQendcopy ( PGconn* conn ) ;
+FUNCTION: int    PQgetline ( PGconn* conn, char* string, int length ) ;
+FUNCTION: int    PQputline ( PGconn* conn, char* string ) ;
+FUNCTION: int    PQgetlineAsync ( PGconn* conn, char* buffer, int bufsize ) ;
+FUNCTION: int    PQputnbytes ( PGconn* conn, char* buffer, int nbytes ) ;
+FUNCTION: int    PQendcopy ( PGconn* conn ) ;
 
 ! Set blocking/nonblocking connection to the backend
-FUNCTION: int   PQsetnonblocking ( PGconn* conn, int arg ) ;
-FUNCTION: int   PQisnonblocking ( PGconn* conn ) ;
+FUNCTION: int    PQsetnonblocking ( PGconn* conn, int arg ) ;
+FUNCTION: int    PQisnonblocking ( PGconn* conn ) ;
 
 ! Force the write buffer to be written (or at least try)
-FUNCTION: int   PQflush ( PGconn* conn ) ;
+FUNCTION: int    PQflush ( PGconn* conn ) ;
 
 ! 
 ! * "Fast path" interface --- not really recommended for application
 ! * use
 !
 FUNCTION: PGresult* PQfn ( PGconn* conn,
-         int fnid,
-         int* result_buf,
-         int* result_len,
-         int result_is_int,
-         PQArgBlock* args,
-         int nargs ) ;
+     int fnid,
+     int* result_buf,
+     int* result_len,
+     int result_is_int,
+     PQArgBlock* args,
+     int nargs ) ;
 
 ! Accessor functions for PGresult objects
 FUNCTION: ExecStatusType PQresultStatus ( PGresult* res ) ;
@@ -313,7 +309,7 @@ FUNCTION: uchar* PQunescapeBytea ( uchar* strtext,
 ! These forms are deprecated!
 FUNCTION: size_t PQescapeString ( void* to, char* from, size_t length ) ;
 FUNCTION: uchar* PQescapeBytea ( uchar* bintext, size_t binlen,
-                          size_t* bytealen ) ;
+              size_t* bytealen ) ;
 
 ! === in fe-print.c ===
 
@@ -332,30 +328,28 @@ FUNCTION: void PQprintTuples ( PGresult* res,
                           int printAttName,
                           int terseOutput,      
                           int width ) ; 
-                                                
 ! === in fe-lobj.c ===
 
 ! Large-object access routines
-FUNCTION: int   lo_open ( PGconn* conn, Oid lobjId, int mode ) ;
-FUNCTION: int   lo_close ( PGconn* conn, int fd ) ;
-FUNCTION: int   lo_read ( PGconn* conn, int fd, char* buf, size_t len ) ;
-FUNCTION: int   lo_write ( PGconn* conn, int fd, char* buf, size_t len ) ;
-FUNCTION: int   lo_lseek ( PGconn* conn, int fd, int offset, int whence ) ;
-FUNCTION: Oid   lo_creat ( PGconn* conn, int mode ) ;
-! FUNCTION: Oid lo_creat ( PGconn* conn, Oid lobjId ) ;
-FUNCTION: int   lo_tell ( PGconn* conn, int fd ) ;
-FUNCTION: int   lo_unlink ( PGconn* conn, Oid lobjId ) ;
-FUNCTION: Oid   lo_import ( PGconn* conn, char* filename ) ;
-FUNCTION: int   lo_export ( PGconn* conn, Oid lobjId, char* filename ) ;
+FUNCTION: int    lo_open ( PGconn* conn, Oid lobjId, int mode ) ;
+FUNCTION: int    lo_close ( PGconn* conn, int fd ) ;
+FUNCTION: int    lo_read ( PGconn* conn, int fd, char* buf, size_t len ) ;
+FUNCTION: int    lo_write ( PGconn* conn, int fd, char* buf, size_t len ) ;
+FUNCTION: int    lo_lseek ( PGconn* conn, int fd, int offset, int whence ) ;
+FUNCTION: Oid    lo_creat ( PGconn* conn, int mode ) ;
+! FUNCTION: Oid    lo_creat ( PGconn* conn, Oid lobjId ) ;
+FUNCTION: int    lo_tell ( PGconn* conn, int fd ) ;
+FUNCTION: int    lo_unlink ( PGconn* conn, Oid lobjId ) ;
+FUNCTION: Oid    lo_import ( PGconn* conn, char* filename ) ;
+FUNCTION: int    lo_export ( PGconn* conn, Oid lobjId, char* filename ) ;
 
 ! === in fe-misc.c ===
 
 ! Determine length of multibyte encoded char at *s
-FUNCTION: int   PQmblen ( uchar* s, int encoding ) ;
+FUNCTION: int    PQmblen ( uchar* s, int encoding ) ;
 
 ! Determine display length of multibyte encoded char at *s
-FUNCTION: int   PQdsplen ( uchar* s, int encoding ) ;
+FUNCTION: int    PQdsplen ( uchar* s, int encoding ) ;
 
 ! Get encoding id from environment variable PGCLIENTENCODING
-FUNCTION: int   PQenv2encoding ( ) ;
-
+FUNCTION: int    PQenv2encoding ( ) ;
diff --git a/extra/db/postgresql/lib/lib.factor b/extra/db/postgresql/lib/lib.factor
new file mode 100644
index 0000000000..a940a42ae4
--- /dev/null
+++ b/extra/db/postgresql/lib/lib.factor
@@ -0,0 +1,44 @@
+! Copyright (C) 2008 Doug Coleman.
+! See http://factorcode.org/license.txt for BSD license.
+USING: arrays continuations db io kernel math namespaces
+quotations sequences db.postgresql.ffi alien alien.c-types ;
+IN: db.postgresql.lib
+
+: postgresql-result-error-message ( res -- str/f )
+    dup zero? [
+        drop f
+    ] [
+        PQresultErrorMessage [ CHAR: \n = ] right-trim
+    ] if ;
+
+: postgres-result-error ( res -- )
+    postgresql-result-error-message [ throw ] when* ;
+
+: postgresql-error-message ( -- str )
+    db get db-handle PQerrorMessage [ CHAR: \n = ] right-trim ;
+
+: postgresql-error ( res -- res )
+    dup [ postgresql-error-message throw ] unless ;
+
+: postgresql-result-ok? ( n -- ? )
+    PQresultStatus
+    PGRES_COMMAND_OK PGRES_TUPLES_OK 2array member? ;
+
+: connect-postgres ( host port pgopts pgtty db user pass -- conn )
+    PQsetdbLogin
+    dup PQstatus zero? [ postgresql-error-message throw ] unless ;
+
+: do-postgresql-statement ( statement -- res )
+    db get db-handle swap statement-sql PQexec dup postgresql-result-ok? [
+        dup postgresql-result-error-message swap PQclear throw
+    ] unless ;
+
+: do-postgresql-bound-statement ( statement -- res )
+    >r db get db-handle r>
+    [ statement-sql ] keep
+    [ statement-params length f ] keep
+    statement-params [ malloc-char-string ] map >c-void*-array
+    f f 0 PQexecParams
+    dup postgresql-result-ok? [
+        dup postgresql-result-error-message swap PQclear throw
+    ] unless ;
diff --git a/extra/db/postgresql/postgresql-tests.factor b/extra/db/postgresql/postgresql-tests.factor
new file mode 100644
index 0000000000..c5a5155d12
--- /dev/null
+++ b/extra/db/postgresql/postgresql-tests.factor
@@ -0,0 +1,110 @@
+! You will need to run  'createdb factor-test' to create the database.
+! Set username and password in  the 'connect' word.
+
+USING: kernel db.postgresql alien continuations io prettyprint
+sequences namespaces tools.test db ;
+IN: temporary
+
+IN: scratchpad
+: test-db ( -- postgresql-db )
+    "localhost" "postgres" "" "factor-test" <postgresql-db> ;
+IN: temporary
+
+[ ] [ test-db [ ] with-db ] unit-test
+
+[ ] [
+    test-db [
+        [ "drop table person;" sql-command ] catch drop
+        "create table person (name varchar(30), country varchar(30));"
+            sql-command
+
+        "insert into person values('John', 'America');" sql-command
+        "insert into person values('Jane', 'New Zealand');" sql-command
+    ] with-db
+] unit-test
+
+[
+    {
+        { "John" "America" }
+        { "Jane" "New Zealand" }
+    }
+] [
+    test-db [
+        "select * from person" sql-query
+    ] with-db
+] unit-test
+
+[
+    { { "John" "America" } }
+] [
+    test-db [
+        "select * from person where name = $1 and country = $2"
+        <simple-statement> [
+            { "Jane" "New Zealand" }
+            over do-bound-query
+
+            { { "Jane" "New Zealand" } } =
+            [ "test fails" throw ] unless
+
+            { "John" "America" }
+            swap do-bound-query
+        ] with-disposal
+    ] with-db
+] unit-test
+
+[
+    {
+        { "John" "America" }
+        { "Jane" "New Zealand" }
+    }
+] [ test-db [ "select * from person" sql-query ] with-db ] unit-test
+
+[
+] [
+    test-db [
+        "insert into person(name, country) values('Jimmy', 'Canada')"
+        sql-command
+    ] with-db
+] unit-test
+
+[
+    {
+        { "John" "America" }
+        { "Jane" "New Zealand" }
+        { "Jimmy" "Canada" }
+    }
+] [ test-db [ "select * from person" sql-query ] with-db ] unit-test
+
+[
+    test-db [
+        [
+            "insert into person(name, country) values('Jose', 'Mexico')" sql-command
+            "insert into person(name, country) values('Jose', 'Mexico')" sql-command
+            "oops" throw
+        ] with-transaction
+    ] with-db
+] unit-test-fails
+
+[ 3 ] [
+    test-db [
+        "select * from person" sql-query length
+    ] with-db
+] unit-test
+
+[
+] [
+    test-db [
+        [
+            "insert into person(name, country) values('Jose', 'Mexico')"
+            sql-command
+            "insert into person(name, country) values('Jose', 'Mexico')"
+            sql-command
+        ] with-transaction
+    ] with-db
+] unit-test
+
+[ 5 ] [
+    test-db [
+        "select * from person" sql-query length
+    ] with-db
+] unit-test
diff --git a/extra/db/postgresql/postgresql.factor b/extra/db/postgresql/postgresql.factor
new file mode 100644
index 0000000000..df778cc80d
--- /dev/null
+++ b/extra/db/postgresql/postgresql.factor
@@ -0,0 +1,105 @@
+! Copyright (C) 2007, 2008 Doug Coleman.
+! See http://factorcode.org/license.txt for BSD license.
+USING: arrays assocs alien alien.syntax continuations io
+kernel math namespaces prettyprint quotations
+sequences debugger db db.postgresql.lib db.postgresql.ffi ;
+IN: db.postgresql
+
+TUPLE: postgresql-db host port pgopts pgtty db user pass ;
+TUPLE: postgresql-statement ;
+TUPLE: postgresql-result-set ;
+: <postgresql-statement> ( statement -- postgresql-statement )
+    postgresql-statement construct-delegate ;
+
+: <postgresql-db> ( host user pass db -- obj )
+    {
+        set-postgresql-db-host
+        set-postgresql-db-user
+        set-postgresql-db-pass
+        set-postgresql-db-db
+    } postgresql-db construct ;
+
+M: postgresql-db db-open ( db -- )
+    dup {
+        postgresql-db-host
+        postgresql-db-port
+        postgresql-db-pgopts
+        postgresql-db-pgtty
+        postgresql-db-db
+        postgresql-db-user
+        postgresql-db-pass
+    } get-slots connect-postgres <db> swap set-delegate ;
+
+M: postgresql-db dispose ( db -- )
+    db-handle PQfinish ;
+
+: with-postgresql ( host ust pass db quot -- )
+    >r <postgresql-db> r> with-disposal ;
+
+M: postgresql-statement bind-statement* ( seq statement -- )
+    set-statement-params ;
+
+M: postgresql-statement rebind-statement ( seq statement -- )
+    bind-statement* ;
+
+M: postgresql-result-set #rows ( result-set -- n )
+    result-set-handle PQntuples ;
+
+M: postgresql-result-set #columns ( result-set -- n )
+    result-set-handle PQnfields ;
+
+M: postgresql-result-set row-column ( result-set n -- obj )
+    >r dup result-set-handle swap result-set-n r> PQgetvalue ;
+
+M: postgresql-statement execute-statement ( statement -- )
+    query-results dispose ;
+
+: increment-n ( result-set -- n )
+    dup result-set-n 1+ dup rot set-result-set-n ;
+
+M: postgresql-statement query-results ( query -- result-set )
+    dup statement-params [
+        over [ bind-statement ] keep
+        do-postgresql-bound-statement
+    ] [
+        dup do-postgresql-statement
+    ] if*
+    postgresql-result-set <result-set>
+    dup init-result-set ;
+
+M: postgresql-result-set advance-row ( result-set -- ? )
+    dup increment-n swap result-set-max >= ;
+
+M: postgresql-statement dispose ( query -- )
+    dup statement-handle PQclear
+    f swap set-statement-handle ;
+
+M: postgresql-result-set dispose ( result-set -- )
+    dup result-set-handle PQclear
+    0 0 f roll {
+        set-result-set-n set-result-set-max set-result-set-handle
+    } set-slots ;
+
+M: postgresql-statement prepare-statement ( statement -- )
+    [
+        >r db get db-handle "" r>
+        dup statement-sql swap statement-params
+        length f PQprepare postgresql-error
+    ] keep set-statement-handle ;
+
+M: postgresql-db <simple-statement> ( sql -- statement )
+    { set-statement-sql } statement construct
+    <postgresql-statement> ;
+
+M: postgresql-db <prepared-statement> ( sql -- statement )
+    { set-statement-sql } statement construct
+    <postgresql-statement> ;
+
+M: postgresql-db begin-transaction ( -- )
+    "BEGIN" sql-command ;
+
+M: postgresql-db commit-transaction ( -- )
+    "COMMIT" sql-command ;
+
+M: postgresql-db rollback-transaction ( -- )
+    "ROLLBACK" sql-command ;
diff --git a/extra/db/sqlite/authors.txt b/extra/db/sqlite/authors.txt
new file mode 100644
index 0000000000..26093b451b
--- /dev/null
+++ b/extra/db/sqlite/authors.txt
@@ -0,0 +1,2 @@
+Chris Double
+Doug Coleman
diff --git a/extra/db/sqlite/ffi/ffi.factor b/extra/db/sqlite/ffi/ffi.factor
new file mode 100644
index 0000000000..609c597b35
--- /dev/null
+++ b/extra/db/sqlite/ffi/ffi.factor
@@ -0,0 +1,130 @@
+! Copyright (C) 2005 Chris Double, Doug Coleman.
+! See http://factorcode.org/license.txt for BSD license.
+!
+! An interface to the sqlite database. Tested against sqlite v3.1.3.
+
+! Not all functions have been wrapped yet. Only those directly involving
+! executing SQL calls and obtaining results.
+
+USING: alien compiler kernel math namespaces sequences strings alien.syntax
+    system combinators ;
+IN: db.sqlite.ffi
+
+<<
+    "sqlite" {
+        { [ winnt? ]  [ "sqlite3.dll" ] }
+        { [ macosx? ] [ "/usr/lib/libsqlite3.dylib" ] }
+        { [ unix? ]  [ "libsqlite3.so" ] }
+    } cond "cdecl" add-library >>
+
+! Return values from sqlite functions
+: SQLITE_OK           0   ; inline ! Successful result
+: SQLITE_ERROR        1   ; inline ! SQL error or missing database
+: SQLITE_INTERNAL     2   ; inline ! An internal logic error in SQLite 
+: SQLITE_PERM         3   ; inline ! Access permission denied 
+: SQLITE_ABORT        4   ; inline ! Callback routine requested an abort 
+: SQLITE_BUSY         5   ; inline ! The database file is locked 
+: SQLITE_LOCKED       6   ; inline ! A table in the database is locked 
+: SQLITE_NOMEM        7   ; inline ! A malloc() failed 
+: SQLITE_READONLY     8   ; inline ! Attempt to write a readonly database 
+: SQLITE_INTERRUPT    9   ; inline ! Operation terminated by sqlite_interrupt() 
+: SQLITE_IOERR       10   ; inline ! Some kind of disk I/O error occurred 
+: SQLITE_CORRUPT     11   ; inline ! The database disk image is malformed 
+: SQLITE_NOTFOUND    12   ; inline ! (Internal Only) Table or record not found 
+: SQLITE_FULL        13   ; inline ! Insertion failed because database is full 
+: SQLITE_CANTOPEN    14   ; inline ! Unable to open the database file 
+: SQLITE_PROTOCOL    15   ; inline ! Database lock protocol error 
+: SQLITE_EMPTY       16   ; inline ! (Internal Only) Database table is empty 
+: SQLITE_SCHEMA      17   ; inline ! The database schema changed 
+: SQLITE_TOOBIG      18   ; inline ! Too much data for one row of a table 
+: SQLITE_CONSTRAINT  19   ; inline ! Abort due to contraint violation 
+: SQLITE_MISMATCH    20   ; inline ! Data type mismatch 
+: SQLITE_MISUSE      21   ; inline ! Library used incorrectly 
+: SQLITE_NOLFS       22   ; inline ! Uses OS features not supported on host 
+: SQLITE_AUTH        23   ; inline ! Authorization denied 
+: SQLITE_FORMAT      24   ; inline ! Auxiliary database format error
+: SQLITE_RANGE       25   ; inline ! 2nd parameter to sqlite3_bind out of range
+: SQLITE_NOTADB      26   ; inline ! File opened that is not a database file
+
+: sqlite-error-messages ( -- seq ) {
+    "Successful result"
+    "SQL error or missing database"
+    "An internal logic error in SQLite"
+    "Access permission denied"
+    "Callback routine requested an abort"
+    "The database file is locked"
+    "A table in the database is locked"
+    "A malloc() failed"
+    "Attempt to write a readonly database"
+    "Operation terminated by sqlite_interrupt()"
+    "Some kind of disk I/O error occurred"
+    "The database disk image is malformed"
+    "(Internal Only) Table or record not found"
+    "Insertion failed because database is full"
+    "Unable to open the database file"
+    "Database lock protocol error"
+    "(Internal Only) Database table is empty"
+    "The database schema changed"
+    "Too much data for one row of a table"
+    "Abort due to contraint violation"
+    "Data type mismatch"
+    "Library used incorrectly"
+    "Uses OS features not supported on host"
+    "Authorization denied"
+    "Auxiliary database format error"
+    "2nd parameter to sqlite3_bind out of range"
+    "File opened that is not a database file"
+} ;
+
+: SQLITE_ROW         100  ; inline ! sqlite_step() has another row ready 
+: SQLITE_DONE        101  ; inline ! sqlite_step() has finished executing 
+
+! Return values from the sqlite3_column_type function
+: SQLITE_INTEGER     1 ; inline
+: SQLITE_FLOAT       2 ; inline
+: SQLITE_TEXT        3 ; inline
+: SQLITE_BLOB        4 ; inline
+: SQLITE_NULL        5 ; inline
+
+! Values for the 'destructor' parameter of the 'bind' routines. 
+: SQLITE_STATIC      0  ; inline
+: SQLITE_TRANSIENT   -1 ; inline
+
+: SQLITE_OPEN_READONLY         HEX: 00000001 ; inline
+: SQLITE_OPEN_READWRITE        HEX: 00000002 ; inline
+: SQLITE_OPEN_CREATE           HEX: 00000004 ; inline
+: SQLITE_OPEN_DELETEONCLOSE    HEX: 00000008 ; inline
+: SQLITE_OPEN_EXCLUSIVE        HEX: 00000010 ; inline
+: SQLITE_OPEN_MAIN_DB          HEX: 00000100 ; inline
+: SQLITE_OPEN_TEMP_DB          HEX: 00000200 ; inline
+: SQLITE_OPEN_TRANSIENT_DB     HEX: 00000400 ; inline
+: SQLITE_OPEN_MAIN_JOURNAL     HEX: 00000800 ; inline
+: SQLITE_OPEN_TEMP_JOURNAL     HEX: 00001000 ; inline
+: SQLITE_OPEN_SUBJOURNAL       HEX: 00002000 ; inline
+: SQLITE_OPEN_MASTER_JOURNAL   HEX: 00004000 ; inline
+
+
+TYPEDEF: void sqlite3
+TYPEDEF: void sqlite3_stmt
+
+LIBRARY: sqlite
+FUNCTION: int sqlite3_open ( char* filename, void* ppDb ) ;
+FUNCTION: int sqlite3_close ( sqlite3* pDb ) ;
+FUNCTION: int sqlite3_prepare ( sqlite3* pDb, char* zSql, int nBytes, void* ppStmt, void* pzTail ) ;
+FUNCTION: int sqlite3_finalize ( sqlite3_stmt* pStmt ) ;
+FUNCTION: int sqlite3_reset ( sqlite3_stmt* pStmt ) ;
+FUNCTION: int sqlite3_step ( sqlite3_stmt* pStmt ) ;
+FUNCTION: int sqlite3_last_insert_rowid ( sqlite3* pStmt ) ;
+FUNCTION: int sqlite3_bind_blob ( sqlite3_stmt* pStmt, int index, void* ptr, int len, int destructor ) ;
+FUNCTION: int sqlite3_bind_int ( sqlite3_stmt* pStmt, int index, int n ) ;
+FUNCTION: int sqlite3_bind_null ( sqlite3_stmt* pStmt, int n ) ;
+FUNCTION: int sqlite3_bind_text ( sqlite3_stmt* pStmt, int index, char* text, int len, int destructor ) ;
+FUNCTION: int sqlite3_bind_parameter_index ( sqlite3_stmt* pStmt, char* name ) ;
+FUNCTION: int sqlite3_column_count ( sqlite3_stmt* pStmt ) ;
+FUNCTION: void* sqlite3_column_blob ( sqlite3_stmt* pStmt, int col ) ;
+FUNCTION: int sqlite3_column_bytes ( sqlite3_stmt* pStmt, int col ) ;
+FUNCTION: char* sqlite3_column_decltype ( sqlite3_stmt* pStmt, int col ) ;
+FUNCTION: int sqlite3_column_int ( sqlite3_stmt* pStmt, int col ) ;
+FUNCTION: int sqlite3_column_name ( sqlite3_stmt* pStmt, int col ) ;
+FUNCTION: char* sqlite3_column_text ( sqlite3_stmt* pStmt, int col ) ;
+FUNCTION: int sqlite3_column_type ( sqlite3_stmt* pStmt, int col ) ;
diff --git a/extra/db/sqlite/lib/lib.factor b/extra/db/sqlite/lib/lib.factor
new file mode 100644
index 0000000000..e5f8425d92
--- /dev/null
+++ b/extra/db/sqlite/lib/lib.factor
@@ -0,0 +1,85 @@
+! Copyright (C) 2008 Chris Double, Doug Coleman.
+! See http://factorcode.org/license.txt for BSD license.
+USING: alien.c-types assocs kernel math math.parser sequences
+db.sqlite.ffi ;
+IN: db.sqlite.lib
+
+TUPLE: sqlite-error n message ;
+
+: sqlite-check-result ( result -- )
+    dup SQLITE_OK = [
+        drop
+    ] [
+        dup sqlite-error-messages nth
+        sqlite-error construct-boa throw
+    ] if ;
+
+: sqlite-open ( filename -- db )
+    "void*" <c-object>
+    [ sqlite3_open sqlite-check-result ] keep *void* ;
+
+: sqlite-close ( db -- )
+    sqlite3_close sqlite-check-result ;
+
+: sqlite-last-insert-rowid ( db -- rowid )
+    sqlite3_last_insert_rowid ;
+
+: sqlite-prepare ( db sql -- statement )
+    #! TODO: Support multiple statements in the SQL string.
+    dup length "void*" <c-object> "void*" <c-object>
+    [ sqlite3_prepare sqlite-check-result ] 2keep
+    drop *void* ;
+
+: sqlite-bind-text ( statement index text -- )
+    dup number? [ number>string ] when
+    dup length SQLITE_TRANSIENT sqlite3_bind_text sqlite-check-result ;
+
+: sqlite-bind-parameter-index ( statement name -- index )
+    sqlite3_bind_parameter_index ;
+
+: sqlite-bind-text-by-name ( statement name text -- )
+    >r dupd sqlite-bind-parameter-index r> sqlite-bind-text ;
+
+: sqlite-bind-assoc ( statement assoc -- )
+    swap [
+        -rot sqlite-bind-text-by-name
+    ] curry assoc-each ;
+
+: sqlite-finalize ( statement -- )
+    sqlite3_finalize sqlite-check-result ;
+
+: sqlite-reset ( statement -- )
+    sqlite3_reset sqlite-check-result ;
+
+: sqlite-#columns ( query -- int )
+    sqlite3_column_count ;
+
+: sqlite-column ( statement index -- string )
+    sqlite3_column_text ;
+
+: sqlite-row ( statement -- seq )
+    dup sqlite-#columns [ sqlite-column ] with map ;
+
+! 2dup sqlite3_column_type .
+! SQLITE_INTEGER     1
+! SQLITE_FLOAT       2
+! SQLITE_TEXT        3
+! SQLITE_BLOB        4
+! SQLITE_NULL        5
+
+: step-complete? ( step-result -- bool )
+    dup SQLITE_ROW =  [
+        drop f
+    ] [
+        dup SQLITE_DONE = [ drop t ] [ sqlite-check-result t ] if
+    ] if ;
+
+: sqlite-step ( prepared -- )
+    dup sqlite3_step step-complete? [
+        drop
+    ] [
+        sqlite-step
+    ] if ;
+
+: sqlite-next ( prepared -- ? )
+    sqlite3_step step-complete? ;
diff --git a/extra/db/sqlite/sqlite-tests.factor b/extra/db/sqlite/sqlite-tests.factor
new file mode 100644
index 0000000000..f64b8d1104
--- /dev/null
+++ b/extra/db/sqlite/sqlite-tests.factor
@@ -0,0 +1,110 @@
+USING: io io.files io.launcher kernel namespaces
+prettyprint tools.test db.sqlite db db.sql sequences
+continuations ;
+IN: temporary
+
+! "sqlite3 -init test.txt test.db"
+
+IN: scratchpad
+: test.db "extra/db/sqlite/test.db" resource-path ;
+
+IN: temporary
+: (create-db) ( -- str )
+    [
+        "sqlite3 -init " %
+        test.db %
+        " " %
+        test.db %
+    ] "" make ;
+
+: create-db ( -- ) (create-db) run-process drop ;
+
+[ ] [ test.db delete-file ] unit-test
+
+[ ] [ create-db ] unit-test
+
+[
+    {
+        { "John" "America" }
+        { "Jane" "New Zealand" }
+    }
+] [
+    test.db [
+        "select * from person" sql-query
+    ] with-sqlite
+] unit-test
+
+[
+    { { "John" "America" } }
+] [
+    test.db [
+        "select * from person where name = :name and country = :country"
+        <simple-statement> [
+            { { ":name" "Jane" } { ":country" "New Zealand" } }
+            over do-bound-query
+
+            { { "Jane" "New Zealand" } } =
+            [ "test fails" throw ] unless
+
+            { { ":name" "John" } { ":country" "America" } }
+            swap do-bound-query
+        ] with-disposal
+    ] with-sqlite
+] unit-test
+
+[
+    {
+        { "1" "John" "America" }
+        { "2" "Jane" "New Zealand" }
+    }
+] [ test.db [ "select rowid, * from person" sql-query ] with-sqlite ] unit-test
+
+[
+] [
+    test.db [
+        "insert into person(name, country) values('Jimmy', 'Canada')"
+        sql-command
+    ] with-sqlite
+] unit-test
+
+[
+    {
+        { "1" "John" "America" }
+        { "2" "Jane" "New Zealand" }
+        { "3" "Jimmy" "Canada" }
+    }
+] [ test.db [ "select rowid, * from person" sql-query ] with-sqlite ] unit-test
+
+[
+    test.db [
+        [
+            "insert into person(name, country) values('Jose', 'Mexico')" sql-command
+            "insert into person(name, country) values('Jose', 'Mexico')" sql-command
+            "oops" throw
+        ] with-transaction
+    ] with-sqlite
+] unit-test-fails
+
+[ 3 ] [
+    test.db [
+        "select * from person" sql-query length
+    ] with-sqlite
+] unit-test
+
+[
+] [
+    test.db [
+        [
+            "insert into person(name, country) values('Jose', 'Mexico')"
+            sql-command
+            "insert into person(name, country) values('Jose', 'Mexico')"
+            sql-command
+        ] with-transaction
+    ] with-sqlite
+] unit-test
+
+[ 5 ] [
+    test.db [
+        "select * from person" sql-query length
+    ] with-sqlite
+] unit-test
diff --git a/extra/db/sqlite/sqlite.factor b/extra/db/sqlite/sqlite.factor
new file mode 100644
index 0000000000..49462dcc50
--- /dev/null
+++ b/extra/db/sqlite/sqlite.factor
@@ -0,0 +1,74 @@
+! Copyright (C) 2005, 2008 Chris Double, Doug Coleman.
+! See http://factorcode.org/license.txt for BSD license.
+USING: alien arrays assocs classes compiler db db.sql
+hashtables io.files kernel math math.parser namespaces
+prettyprint sequences strings tuples alien.c-types
+continuations db.sqlite.lib db.sqlite.ffi ;
+IN: db.sqlite
+
+TUPLE: sqlite-db path ;
+C: <sqlite-db> sqlite-db
+
+M: sqlite-db db-open ( db -- )
+    dup sqlite-db-path sqlite-open <db>
+    swap set-delegate ;
+
+M: sqlite-db dispose ( obj -- )
+    dup db-handle sqlite-close
+    f over set-db-handle
+    f swap set-delegate ;
+
+: with-sqlite ( path quot -- )
+    >r <sqlite-db> r> with-db ; inline
+
+TUPLE: sqlite-statement ;
+C: <sqlite-statement> sqlite-statement
+
+TUPLE: sqlite-result-set ;
+: <sqlite-result-set> ( query -- sqlite-result-set )
+    dup statement-handle sqlite-result-set <result-set> ;
+
+M: sqlite-db <simple-statement> ( str -- obj )
+    <prepared-statement> ;
+
+M: sqlite-db <prepared-statement> ( str -- obj )
+    db get db-handle over sqlite-prepare
+    { set-statement-sql set-statement-handle } statement construct
+    <sqlite-statement> [ set-delegate ] keep ;
+
+M: sqlite-statement dispose ( statement -- )
+    statement-handle sqlite-finalize ;
+
+M: sqlite-result-set dispose ( result-set -- )
+    f swap set-result-set-handle ;
+
+M: sqlite-statement bind-statement* ( assoc statement -- )
+    statement-handle swap sqlite-bind-assoc ;
+
+M: sqlite-statement rebind-statement ( assoc statement -- )
+    dup statement-handle sqlite-reset
+    statement-handle swap sqlite-bind-assoc ;
+
+M: sqlite-statement execute-statement ( statement -- )
+    statement-handle sqlite-next drop ;
+
+M: sqlite-result-set #columns ( result-set -- n )
+    result-set-handle sqlite-#columns ;
+
+M: sqlite-result-set row-column ( result-set n -- obj )
+    >r result-set-handle r> sqlite-column ;
+
+M: sqlite-result-set advance-row ( result-set -- handle ? )
+    result-set-handle sqlite-next ;
+
+M: sqlite-statement query-results ( query -- result-set )
+    dup statement-handle sqlite-result-set <result-set> ;
+
+M: sqlite-db begin-transaction ( -- )
+    "BEGIN" sql-command ;
+
+M: sqlite-db commit-transaction ( -- )
+    "COMMIT" sql-command ;
+
+M: sqlite-db rollback-transaction ( -- )
+    "ROLLBACK" sql-command ;
diff --git a/extra/db/sqlite/test.txt b/extra/db/sqlite/test.txt
new file mode 100644
index 0000000000..e4487d30f9
--- /dev/null
+++ b/extra/db/sqlite/test.txt
@@ -0,0 +1,3 @@
+create table person (name varchar(30), country varchar(30));
+insert into person values('John', 'America');
+insert into person values('Jane', 'New Zealand');
diff --git a/extra/http/client/client.factor b/extra/http/client/client.factor
index dde2c7d205..8e6d8257a4 100755
--- a/extra/http/client/client.factor
+++ b/extra/http/client/client.factor
@@ -2,7 +2,7 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: assocs http kernel math math.parser namespaces sequences
 io io.sockets io.streams.string io.files strings splitting
-continuations ;
+continuations assocs.lib ;
 IN: http.client
 
 : parse-host ( url -- host port )
@@ -44,7 +44,7 @@ DEFER: http-get-stream
     #! Should this support Location: headers that are
     #! relative URLs?
     pick 100 /i 3 = [
-        dispose "Location" swap at nip http-get-stream
+        dispose "location" swap peek-at nip http-get-stream
     ] when ;
 
 : http-get-stream ( url -- code headers stream )
diff --git a/extra/http/http.factor b/extra/http/http.factor
index 1bd9e18d98..755f36a538 100755
--- a/extra/http/http.factor
+++ b/extra/http/http.factor
@@ -1,11 +1,12 @@
 ! Copyright (C) 2003, 2007 Slava Pestov.
 ! See http://factorcode.org/license.txt for BSD license.
 USING: hashtables io kernel math namespaces math.parser assocs
-sequences strings splitting ascii io.utf8 ;
+sequences strings splitting ascii io.utf8 assocs.lib
+namespaces unicode.case ;
 IN: http
 
 : header-line ( line -- )
-    ": " split1 dup [ swap set ] [ 2drop ] if ;
+    ": " split1 dup [ swap >lower insert ] [ 2drop ] if ;
 
 : (read-header) ( -- )
     readln dup
@@ -71,4 +72,3 @@ IN: http
             hash>query %
         ] if
     ] "" make ;
-
diff --git a/extra/http/server/responders/responders.factor b/extra/http/server/responders/responders.factor
index 8dcaa7223d..70503236f6 100644
--- a/extra/http/server/responders/responders.factor
+++ b/extra/http/server/responders/responders.factor
@@ -2,7 +2,7 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: arrays assocs hashtables html html.elements splitting
 http io kernel math math.parser namespaces parser sequences
-strings io.server ;
+strings io.server vectors assocs.lib ;
 
 IN: http.server.responders
 
@@ -10,8 +10,11 @@ IN: http.server.responders
 SYMBOL: vhosts
 SYMBOL: responders
 
+: >header ( value key -- multi-hash )
+    H{ } clone [ insert-at ] keep ;
+
 : print-header ( alist -- )
-    [ swap write ": " write print ] assoc-each nl ;
+    [ swap write ": " write print ] multi-assoc-each nl ;
 
 : response ( msg -- ) "HTTP/1.0 " write print ;
 
@@ -20,7 +23,7 @@ SYMBOL: responders
 
 : error-head ( error -- )
     dup log-error response
-    H{ { "Content-Type" "text/html" } } print-header nl ;
+    H{ { "Content-Type" V{ "text/html" } } } print-header nl ;
 
 : httpd-error ( error -- )
     #! This must be run from handle-request
@@ -36,7 +39,7 @@ SYMBOL: responders
 
 : serving-content ( mime -- )
     "200 Document follows" response
-    "Content-Type" associate print-header ;
+    "Content-Type" >header print-header ;
 
 : serving-html "text/html" serving-content ;
 
@@ -46,7 +49,7 @@ SYMBOL: responders
 : serving-text "text/plain" serving-content ;
 
 : redirect ( to response -- )
-    response "Location" associate print-header ;
+    response "Location" >header print-header ;
 
 : permanent-redirect ( to -- )
     "301 Moved Permanently" redirect ;
@@ -84,14 +87,14 @@ SYMBOL: max-post-request
 : log-headers ( hash -- )
     [
         drop {
-            "User-Agent"
-            "Referer"
-            "X-Forwarded-For"
-            "Host"
+            "user-agent"
+            "referer"
+            "x-forwarded-for"
+            "host"
         } member?
     ] assoc-subset [
         ": " swap 3append log-message
-    ] assoc-each ;
+    ] multi-assoc-each ;
 
 : prepare-url ( url -- url )
     #! This is executed in the with-request namespace.
@@ -122,7 +125,8 @@ SYMBOL: max-post-request
 
 : query-param ( key -- value ) "query" get at ;
 
-: header-param ( key -- value ) "header" get at ;
+: header-param ( key -- value )
+    "header" get peek-at ;
 
 : host ( -- string )
     #! The host the current responder was called from.
@@ -130,7 +134,7 @@ SYMBOL: max-post-request
 
 : add-responder ( responder -- )
     #! Add a responder object to the list.
-    "responder" over at  responders get set-at ;
+    "responder" over at responders get set-at ;
 
 : make-responder ( quot -- )
     #! quot has stack effect ( url -- )
diff --git a/extra/io/unix/launcher/launcher.factor b/extra/io/unix/launcher/launcher.factor
index 030583dbe8..b44ac80159 100755
--- a/extra/io/unix/launcher/launcher.factor
+++ b/extra/io/unix/launcher/launcher.factor
@@ -57,8 +57,8 @@ MEMO: 'arguments' ( -- parser )
 : setup-redirection ( -- )
     +stdin+ get read-flags 0 redirect
     +stdout+ get write-flags 1 redirect
-    +stderr+ get dup +stdout+ get eq?
-    [ 1 2 dup2 ] [ write-flags 2 redirect ] if ;
+    +stderr+ get dup +stdout+ eq?
+    [ drop 1 2 dup2 io-error ] [ write-flags 2 redirect ] if ;
 
 : spawn-process ( -- )
     [
diff --git a/extra/line-art/line-art.factor b/extra/line-art/line-art.factor
deleted file mode 100644
index 1a0ae6993f..0000000000
--- a/extra/line-art/line-art.factor
+++ /dev/null
@@ -1,255 +0,0 @@
-USING: arrays bunny combinators.lib continuations io io.files kernel
-       math math.functions math.vectors multiline
-       namespaces debugger
-       opengl opengl.gl opengl-demo-support
-       prettyprint
-       sequences ui ui.gadgets ui.gestures ui.render ;
-IN: line-art
-
-TUPLE: line-art-gadget
-    model step1-program step2-program
-    framebuffer color-texture normal-texture depth-texture framebuffer-dim ;
-
-: <line-art-gadget> ( -- line-art-gadget )
-    40.0 -5.0 0.275 <demo-gadget>
-    maybe-download read-model
-    { set-delegate set-line-art-gadget-model } line-art-gadget construct ;
-
-STRING: line-art-step1-vertex-shader-source
-varying vec3 normal;
-
-void
-main()
-{
-    gl_Position = ftransform();
-    normal = gl_Normal;
-}
-
-;
-
-STRING: line-art-step1-fragment-shader-source
-varying vec3 normal;
-uniform vec4 color;
-
-void
-main()
-{
-    gl_FragData[0] = color;
-    gl_FragData[1] = vec4(normal, 1);
-}
-
-;
-
-STRING: line-art-step2-vertex-shader-source
-varying vec2 coord;
-
-void
-main()
-{
-    gl_Position = ftransform();
-    coord = (gl_Vertex * vec4(0.5) + vec4(0.5)).xy;
-}
-
-;
-
-STRING: line-art-step2-fragment-shader-source
-uniform sampler2D colormap, normalmap, depthmap;
-uniform vec4 line_color;
-varying vec2 coord;
-
-const float DEPTH_RATIO_THRESHOLD = 1.001, NORMAL_DOT_THRESHOLD = 1.0, SAMPLE_SPREAD = 1.0/512.0;
-
-bool
-is_normal_border(vec3 norm1, vec3 norm2)
-{
-    return dot(norm1, norm2) < NORMAL_DOT_THRESHOLD;
-}
-
-float
-depth_sample(vec2 c)
-{
-    return texture2D(depthmap, c).x;
-}
-bool
-are_depths_border(vec3 depths)
-{
-    return any(lessThan(depths, vec3(1.0/DEPTH_RATIO_THRESHOLD)))
-        || any(greaterThan(depths, vec3(DEPTH_RATIO_THRESHOLD)));
-}
-
-vec3
-normal_sample(vec2 c)
-{
-    return texture2D(normalmap, c).xyz;
-}
-
-float
-min6(float a, float b, float c, float d, float e, float f)
-{
-    return min(min(min(min(min(a, b), c), d), e), f);
-}
-
-float
-border_factor(vec2 c)
-{
-    vec2 coord1 = c + vec2(-SAMPLE_SPREAD, -SAMPLE_SPREAD),
-         coord2 = c + vec2( SAMPLE_SPREAD, -SAMPLE_SPREAD),
-         coord3 = c + vec2(-SAMPLE_SPREAD,  SAMPLE_SPREAD),
-         coord4 = c + vec2( SAMPLE_SPREAD,  SAMPLE_SPREAD);
-    
-    vec4 depths = vec4(depth_sample(coord1),
-                       depth_sample(coord2),
-                       depth_sample(coord3),
-                       depth_sample(coord4));
-    if (depths == vec4(1, 1, 1, 1))
-        return 0.0;
-    
-    vec3 ratios1 = depths.xxx/depths.yzw, ratios2 = depths.yyz/depths.zww;
-    
-    if (are_depths_border(ratios1) || are_depths_border(ratios2))
-        return 1.0;
-    
-    vec3 normal1 = normal_sample(coord1),
-         normal2 = normal_sample(coord2),
-         normal3 = normal_sample(coord3),
-         normal4 = normal_sample(coord4);
-    
-    float normal_border = 1.0 - min6(
-        dot(normal1, normal2),
-        dot(normal1, normal3),
-        dot(normal1, normal4),
-        dot(normal2, normal3),
-        dot(normal2, normal4),
-        dot(normal3, normal4)
-    );
-    
-    return normal_border;
-}
-
-void
-main()
-{
-    gl_FragColor = mix(texture2D(colormap, coord), line_color, border_factor(coord));
-}
-
-;
-
-: (line-art-step1-program) ( -- step1 )
-    line-art-step1-vertex-shader-source line-art-step1-fragment-shader-source
-    <simple-gl-program> ;
-: (line-art-step2-program) ( -- step2 )
-    line-art-step2-vertex-shader-source line-art-step2-fragment-shader-source
-    <simple-gl-program> ;
-
-: (line-art-framebuffer-texture) ( dim iformat xformat -- texture )
-    swapd >r >r >r
-    GL_TEXTURE0 glActiveTexture
-    gen-texture GL_TEXTURE_2D over glBindTexture
-    GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_CLAMP glTexParameteri
-    GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_CLAMP glTexParameteri
-    GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_NEAREST glTexParameteri
-    GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_NEAREST glTexParameteri
-    GL_TEXTURE_2D 0 r> r> first2 0 r> GL_UNSIGNED_BYTE f glTexImage2D ;
-
-: (line-art-color-texture) ( dim -- texture )
-    GL_RGBA16F_ARB GL_RGBA (line-art-framebuffer-texture) ;
-
-: (line-art-normal-texture) ( dim -- texture )
-    GL_RGBA16F_ARB GL_RGBA (line-art-framebuffer-texture) ;
-
-: (line-art-depth-texture) ( dim -- texture )
-    GL_DEPTH_COMPONENT32 GL_DEPTH_COMPONENT (line-art-framebuffer-texture) ;
-
-: (attach-framebuffer-texture) ( texture attachment -- )
-    swap >r >r GL_FRAMEBUFFER_EXT r> GL_TEXTURE_2D r> 0 glFramebufferTexture2DEXT gl-error ;
-
-: (line-art-framebuffer) ( color-texture normal-texture depth-texture -- framebuffer )
-    3array gen-framebuffer dup [
-        swap GL_COLOR_ATTACHMENT0_EXT
-             GL_COLOR_ATTACHMENT1_EXT
-             GL_DEPTH_ATTACHMENT_EXT 3array [ (attach-framebuffer-texture) ] 2each
-        check-framebuffer
-    ] with-framebuffer ;
-    
-: line-art-remake-framebuffer-if-needed ( gadget -- )
-    dup { rect-dim rect-dim line-art-gadget-framebuffer-dim } get-slots = [ 2drop ] [
-        swap >r
-        dup (line-art-color-texture) gl-error
-        swap dup (line-art-normal-texture) gl-error
-        swap dup (line-art-depth-texture) gl-error
-        swap >r
-        [ (line-art-framebuffer) ] 3keep
-        r> r> { set-line-art-gadget-framebuffer
-                set-line-art-gadget-color-texture
-                set-line-art-gadget-normal-texture
-                set-line-art-gadget-depth-texture
-                set-line-art-gadget-framebuffer-dim } set-slots
-    ] if ;
-    
-M: line-art-gadget graft* ( gadget -- )
-    [ "2.0" { "GL_ARB_draw_buffers"
-            "GL_ARB_shader_objects"
-            "GL_ARB_multitexture"
-            "GL_ARB_texture_float" }
-    require-gl-version-or-extensions
-    { "GL_EXT_framebuffer_object" } require-gl-extensions
-    GL_CULL_FACE glEnable
-    GL_DEPTH_TEST glEnable
-    (line-art-step1-program) over set-line-art-gadget-step1-program
-    (line-art-step2-program) swap set-line-art-gadget-step2-program
-    ] [ ] [ :c ] cleanup ;
-
-M: line-art-gadget ungraft* ( gadget -- )
-    dup line-art-gadget-framebuffer [
-        { [ line-art-gadget-step1-program [ delete-gl-program ] when* ]
-          [ line-art-gadget-step2-program [ delete-gl-program ] when* ]
-          [ line-art-gadget-framebuffer [ delete-framebuffer ] when* ]
-          [ line-art-gadget-color-texture [ delete-texture ] when* ]
-          [ line-art-gadget-normal-texture [ delete-texture ] when* ]
-          [ line-art-gadget-depth-texture [ delete-texture ] when* ]
-          [ f swap set-line-art-gadget-framebuffer-dim ]
-          [ f swap set-line-art-gadget-framebuffer ] } call-with
-    ] [ drop ] if ;
-
-: line-art-draw-setup ( gadget -- gadget )
-    0.0 0.0 0.0 1.0 glClearColor
-    GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT bitor glClear
-    dup demo-gadget-set-matrices
-    dup line-art-remake-framebuffer-if-needed
-    gl-error ;
-
-: line-art-clear-framebuffer ( -- )
-    GL_COLOR_ATTACHMENT0_EXT glDrawBuffer
-    0.2 0.2 0.2 1.0 glClearColor
-    GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT bitor glClear
-    GL_COLOR_ATTACHMENT1_EXT glDrawBuffer
-    0.0 0.0 0.0 0.0 glClearColor
-    GL_COLOR_BUFFER_BIT glClear ;
-
-M: line-art-gadget draw-gadget* ( gadget -- )
-    line-art-draw-setup
-    dup line-art-gadget-framebuffer [
-        line-art-clear-framebuffer
-        { GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT1_EXT } set-draw-buffers
-        dup line-art-gadget-step1-program dup [
-            "color" glGetUniformLocation 0.6 0.5 0.5 1.0 glUniform4f
-            0.0 -0.12 0.0 glTranslatef
-            dup line-art-gadget-model first3 draw-bunny
-        ] with-gl-program
-    ] with-framebuffer
-    init-matrices
-    dup line-art-gadget-color-texture GL_TEXTURE_2D GL_TEXTURE0 bind-texture-unit
-    dup line-art-gadget-normal-texture GL_TEXTURE_2D GL_TEXTURE1 bind-texture-unit
-    dup line-art-gadget-depth-texture GL_TEXTURE_2D GL_TEXTURE2 bind-texture-unit
-    line-art-gadget-step2-program dup [
-        { [ "colormap"  glGetUniformLocation 0 glUniform1i ]
-          [ "normalmap" glGetUniformLocation 1 glUniform1i ]
-          [ "depthmap"  glGetUniformLocation 2 glUniform1i ]
-          [ "line_color" glGetUniformLocation 0.2 0.0 0.0 1.0 glUniform4f ] } call-with
-        { -1.0 -1.0 } { 1.0 1.0 } rect-vertices
-    ] with-gl-program ;
-
-: line-art-window ( -- )
-    [ <line-art-gadget> "Line Art" open-window ] with-ui ;
-    
-MAIN: line-art-window
diff --git a/extra/opengl-demo-support/authors.txt b/extra/opengl/demo-support/authors.txt
similarity index 100%
rename from extra/opengl-demo-support/authors.txt
rename to extra/opengl/demo-support/authors.txt
diff --git a/extra/opengl-demo-support/opengl-demo-support.factor b/extra/opengl/demo-support/demo-support.factor
similarity index 99%
rename from extra/opengl-demo-support/opengl-demo-support.factor
rename to extra/opengl/demo-support/demo-support.factor
index ecc6458d41..59b7a3bcc3 100644
--- a/extra/opengl-demo-support/opengl-demo-support.factor
+++ b/extra/opengl/demo-support/demo-support.factor
@@ -1,6 +1,6 @@
 USING: arrays combinators.lib kernel math math.functions math.vectors namespaces
        opengl opengl.gl sequences ui ui.gadgets ui.gestures ui.render ;
-IN: opengl-demo-support
+IN: opengl.demo-support
 
 : NEAR-PLANE 1.0 64.0 / ; inline
 : FAR-PLANE 4.0 ; inline
diff --git a/extra/opengl-demo-support/summary.txt b/extra/opengl/demo-support/summary.txt
similarity index 100%
rename from extra/opengl-demo-support/summary.txt
rename to extra/opengl/demo-support/summary.txt
diff --git a/extra/opengl-demo-support/tags.txt b/extra/opengl/demo-support/tags.txt
similarity index 100%
rename from extra/opengl-demo-support/tags.txt
rename to extra/opengl/demo-support/tags.txt
diff --git a/extra/opengl/opengl-docs.factor b/extra/opengl/opengl-docs.factor
index cc8221baa1..cb0c9e884f 100644
--- a/extra/opengl/opengl-docs.factor
+++ b/extra/opengl/opengl-docs.factor
@@ -1,5 +1,5 @@
 USING: help.markup help.syntax io kernel math quotations
-opengl.gl ;
+opengl.gl multiline assocs ;
 IN: opengl
 
 HELP: gl-color
@@ -65,7 +65,7 @@ HELP: gen-renderbuffer
 { $values { "id" integer } }
 { $description "Wrapper for " { $link glGenRenderbuffersEXT } " to handle the common case of generating a single render buffer ID." } ;
 
-HELP: gen-buffer
+HELP: gen-gl-buffer
 { $values { "id" integer } }
 { $description "Wrapper for " { $link glGenBuffers } " to handle the common case of generating a single buffer ID." } ;
 
@@ -81,14 +81,14 @@ HELP: delete-renderbuffer
 { $values { "id" integer } }
 { $description "Wrapper for " { $link glDeleteRenderbuffersEXT } " to handle the common case of deleting a single render buffer ID." } ;
 
-HELP: delete-buffer
+HELP: delete-gl-buffer
 { $values { "id" integer } }
 { $description "Wrapper for " { $link glDeleteBuffers } " to handle the common case of deleting a single buffer ID." } ;
 
 { gen-texture delete-texture } related-words
 { gen-framebuffer delete-framebuffer } related-words
 { gen-renderbuffer delete-renderbuffer } related-words
-{ gen-buffer delete-buffer } related-words
+{ gen-gl-buffer delete-gl-buffer } related-words
 
 HELP: framebuffer-incomplete?
 { $values { "status/f" "The framebuffer error code, or " { $snippet "f" } " if the framebuffer is render-complete." } }
@@ -241,8 +241,19 @@ HELP: delete-gl-program
 { $description "Deletes the program object, invalidating it and releasing any resources allocated for it by the OpenGL implementation. Any attached " { $link gl-shader } "s are also deleted.\n\nIf the shader objects should be preserved, they should each be detached using " { $link detach-gl-program-shader } ". The program object can then be destroyed alone using " { $link delete-gl-program-only } "." } ;
 
 HELP: with-gl-program
-{ $values { "program" "A " { $link gl-program } " object" } { "quot" "A quotation" } }
-{ $description "Enables " { $snippet "program" } " for all OpenGL calls made in the dynamic extent of " { $snippet "quot" } ". The fixed-function pipeline is restored at the end of " { $snippet "quot" } "." } ;
+{ $values { "program" "A " { $link gl-program } " object" } { "uniforms" "An " { $link assoc } " between uniform parameter names and quotations with effect " { $snippet "( uniform-location -- )" } } { "quot" "A quotation" } }
+{ $description "Enables " { $snippet "program" } " for all OpenGL calls made in the dynamic extent of " { $snippet "quot" } ". The fixed-function pipeline is restored at the end of " { $snippet "quot" } ". Before calling " { $snippet "quot" } ", calls " { $link glGetUniformLocation } " on each key of " { $snippet "uniforms" } " to get the address of the uniform parameter, which is then placed on top of the stack for the associated quotation.\n\nExample:" }
+{ $code <"
+! From bunny.cel-shaded
+: (draw-cel-shaded-bunny) ( geom program -- )
+    {
+        { "light_direction" [ 1.0 -1.0 1.0 glUniform3f ] }
+        { "color"           [ 0.6 0.5 0.5 1.0 glUniform4f ] }
+        { "ambient"         [ 0.2 0.2 0.2 0.2 glUniform4f ] }
+        { "diffuse"         [ 0.8 0.8 0.8 0.8 glUniform4f ] }
+        { "shininess"       [ 100.0 glUniform1f ] }
+    } [ bunny-geom ] with-gl-program ;
+"> } ;
 
 HELP: gl-version
 { $values { "version" "The version string from the OpenGL implementation" } }
@@ -284,15 +295,19 @@ HELP: has-gl-extensions?
 { $values { "extensions" "A sequence of extension name strings" } { "?" "A boolean value" } }
 { $description "Returns true if the set of " { $snippet "extensions" } " is a subset of the implementation-supported extensions returned by " { $link gl-extensions } "." } ;
 
+HELP: has-gl-version-or-extensions?
+{ $values { "version" "A version string" } { "extensions" "A sequence of extension name strings" } }
+{ $description "Returns true if either " { $link has-gl-version? } " or " { $link has-gl-extensions? } " returns true for " { $snippet "version" } " or " { $snippet "extensions" } ", respectively. Intended for use when required OpenGL functionality can be verified either by a minimum version or a set of equivalent extensions." } ;
+
 HELP: require-gl-extensions
 { $values { "extensions" "A sequence of extension name strings" } }
 { $description "Throws an exception if " { $link has-gl-extensions? } " returns false for " { $snippet "extensions" } "." } ;
 
 HELP: require-gl-version-or-extensions
 { $values { "version" "A version string" } { "extensions" "A sequence of extension name strings" } }
-{ $description "Throws an exception if neither " { $link has-gl-version? } " nor " { $link has-gl-extensions? } " returns true for " { $snippet "version" } " or " { $snippet "extensions" } ", respectively. Intended for use when required OpenGL functionality can be verified either by a minimum version, or a set of equivalent extensions." } ;
+{ $description "Throws an exception if neither " { $link has-gl-version? } " nor " { $link has-gl-extensions? } " returns true for " { $snippet "version" } " or " { $snippet "extensions" } ", respectively. Intended for use when required OpenGL functionality can be verified either by a minimum version or a set of equivalent extensions." } ;
 
-{ require-gl-version require-glsl-version require-gl-extensions require-gl-version-or-extensions has-gl-version? has-glsl-version? has-gl-extensions? gl-version glsl-version gl-extensions } related-words
+{ require-gl-version require-glsl-version require-gl-extensions require-gl-version-or-extensions has-gl-version? has-glsl-version? has-gl-extensions? has-gl-version-or-extensions? gl-version glsl-version gl-extensions } related-words
 
 ARTICLE: "gl-utilities" "OpenGL utility words"
 "In addition to the full OpenGL API, the " { $vocab-link "opengl" } " vocabulary includes some utility words to give OpenGL a more Factor-like feel."
diff --git a/extra/opengl/opengl.factor b/extra/opengl/opengl.factor
index 22bf657637..071f85fe12 100755
--- a/extra/opengl/opengl.factor
+++ b/extra/opengl/opengl.factor
@@ -5,7 +5,7 @@
 USING: alien alien.c-types continuations kernel libc math macros
 namespaces math.vectors math.constants math.functions
 math.parser opengl.gl opengl.glu combinators arrays sequences
-splitting words byte-arrays assocs ;
+splitting words byte-arrays assocs combinators.lib ;
 IN: opengl
 
 : coordinates [ first2 ] 2apply ;
@@ -30,6 +30,13 @@ IN: opengl
 
 : do-enabled ( what quot -- )
     over glEnable dip glDisable ; inline
+: do-enabled-client-state ( what quot -- )
+    over glEnableClientState dip glDisableClientState ; inline
+
+: all-enabled ( seq quot -- )
+    over [ glEnable ] each dip [ glDisable ] each ; inline
+: all-enabled-client-state ( seq quot -- )
+    over [ glEnableClientState ] each dip [ glDisableClientState ] each ; inline
 
 : do-matrix ( mode quot -- )
     swap [ glMatrixMode glPushMatrix call ] keep
@@ -103,7 +110,7 @@ IN: opengl
     [ glGenFramebuffersEXT ] (gen-gl-object) ;
 : gen-renderbuffer ( -- id )
     [ glGenRenderbuffersEXT ] (gen-gl-object) ;
-: gen-buffer ( -- id )
+: gen-gl-buffer ( -- id )
     [ glGenBuffers ] (gen-gl-object) ;
 
 : (delete-gl-object) ( id quot -- )
@@ -114,9 +121,26 @@ IN: opengl
     [ glDeleteFramebuffersEXT ] (delete-gl-object) ;
 : delete-renderbuffer ( id -- )
     [ glDeleteRenderbuffersEXT ] (delete-gl-object) ;
-: delete-buffer ( id -- )
+: delete-gl-buffer ( id -- )
     [ glDeleteBuffers ] (delete-gl-object) ;
 
+: with-gl-buffer ( binding id quot -- )
+    -rot dupd glBindBuffer
+    [ slip ] [ 0 glBindBuffer ] [ ] cleanup ; inline
+
+: with-array-element-buffers ( array-buffer element-buffer quot -- )
+    -rot GL_ELEMENT_ARRAY_BUFFER swap [
+        swap GL_ARRAY_BUFFER -rot with-gl-buffer
+    ] with-gl-buffer ; inline
+
+: <gl-buffer> ( target data hint -- id )
+    pick gen-gl-buffer [ [
+        >r dup byte-length swap r> glBufferData
+    ] with-gl-buffer ] keep ;
+
+: buffer-offset ( int -- alien )
+    <alien> ; inline
+
 : framebuffer-incomplete? ( -- status/f )
     GL_FRAMEBUFFER_EXT glCheckFramebufferStatusEXT
     dup GL_FRAMEBUFFER_COMPLETE_EXT = f rot ? ;
@@ -256,7 +280,7 @@ TUPLE: sprite loc dim dim2 dlist texture ;
 : c-true? ( int -- ? ) zero? not ; inline
 
 : with-gl-shader-source-ptr ( string quot -- )
-    swap >byte-array malloc-byte-array [
+    swap string>char-alien malloc-byte-array [
         <void*> swap call
     ] keep free ; inline
 
@@ -295,9 +319,8 @@ TUPLE: sprite loc dim dim2 dlist texture ;
     GL_INFO_LOG_LENGTH gl-shader-get-int ; inline
 
 : gl-shader-info-log ( shader -- log )
-    dup gl-shader-info-log-length
-    dup [
-        0 <int> over glGetShaderInfoLog
+    dup gl-shader-info-log-length dup [
+        [ 0 <int> swap glGetShaderInfoLog ] keep
         alien>char-string
     ] with-malloc ;
 
@@ -331,9 +354,10 @@ PREDICATE: gl-shader fragment-shader (fragment-shader?) ;
     GL_INFO_LOG_LENGTH gl-program-get-int ; inline
 
 : gl-program-info-log ( program -- log )
-    dup gl-program-info-log-length
-    dup [ [ 0 <int> swap glGetProgramInfoLog ] keep
-          alien>char-string ] with-malloc ;
+    dup gl-program-info-log-length dup [
+        [ 0 <int> swap glGetProgramInfoLog ] keep
+        alien>char-string
+    ] with-malloc ;
 
 : check-gl-program ( program -- program* )
     dup gl-program-ok? [ dup gl-program-info-log throw ] unless ;
@@ -343,7 +367,8 @@ PREDICATE: gl-shader fragment-shader (fragment-shader?) ;
 
 : gl-program-shaders ( program -- shaders )
     dup gl-program-shaders-length [
-        dup "GLuint" <c-array> 0 <int> over glGetAttachedShaders
+        dup "GLuint" <c-array>
+        [ 0 <int> swap glGetAttachedShaders ] keep
     ] keep c-uint-array> ;
 
 : delete-gl-program-only ( program -- )
@@ -357,9 +382,23 @@ PREDICATE: gl-shader fragment-shader (fragment-shader?) ;
         2dup detach-gl-program-shader delete-gl-shader
     ] each delete-gl-program-only ;
 
-: with-gl-program ( program quot -- )
+: (with-gl-program) ( program quot -- )
     swap glUseProgram [ 0 glUseProgram ] [ ] cleanup ; inline
 
+: (with-gl-program-uniforms) ( uniforms -- quot )
+    [ [ swap , \ glGetUniformLocation , % ] [ ] make ]
+    { } assoc>map ;
+: (make-with-gl-program) ( uniforms quot -- q )
+    [
+        \ dup ,
+        [ swap (with-gl-program-uniforms) , \ call-with , % ]
+        [ ] make ,
+        \ (with-gl-program) ,
+    ] [ ] make ;
+
+MACRO: with-gl-program ( uniforms quot -- )
+    (make-with-gl-program) ;
+
 PREDICATE: integer gl-program (gl-program?) ;
 
 : <simple-gl-program> ( vertex-shader-source fragment-shader-source -- program )
@@ -376,7 +415,7 @@ PREDICATE: integer gl-program (gl-program?) ;
 : gl-extensions ( -- seq )
     GL_EXTENSIONS glGetString " " split ;
 : has-gl-extensions? ( extensions -- ? )
-    gl-extensions subseq? ;
+    gl-extensions swap [ over member? ] all? nip ;
 : (make-gl-extensions-error) ( required-extensions -- )
     gl-extensions swap seq-diff
     "Required OpenGL extensions not supported:\n" %
@@ -420,8 +459,11 @@ PREDICATE: integer gl-program (gl-program?) ;
     [ "Required GLSL version " % % " not supported (" % glsl-version % " available)" % ]
     (require-gl) ;
 
+: has-gl-version-or-extensions? ( version extensions -- ? )
+    has-gl-extensions? swap has-gl-version? or ;
+
 : require-gl-version-or-extensions ( version extensions -- )
-    2array [ first2 has-gl-extensions? swap has-gl-version? or ]
-    [ dup first (make-gl-version-error) "\n" %
-      second (make-gl-extensions-error) "\n" % ]
-    (require-gl) ;
+    2array [ first2 has-gl-version-or-extensions? ] [
+        dup first (make-gl-version-error) "\n" %
+        second (make-gl-extensions-error) "\n" %
+    ] (require-gl) ;
diff --git a/extra/postgresql/postgresql-tests.factor b/extra/postgresql/postgresql-tests.factor
deleted file mode 100644
index c725882b67..0000000000
--- a/extra/postgresql/postgresql-tests.factor
+++ /dev/null
@@ -1,42 +0,0 @@
-! You will need to run  'createdb factor-test' to create the database.
-! Set username and password in  the 'connect' word.
-
-IN: postgresql-test
-USING: kernel postgresql alien continuations io prettyprint
-sequences namespaces ;
-
-
-: test-connection ( host port pgopts pgtty db user pass -- bool )
-    [ [ ] with-postgres ] catch "Error connecting!" "Connected!" ? print ;
-
-! just a basic demo
-
-"localhost" "" "" "" "test" "postgres" "" [
-    "drop table animal" do-command
-
-    "create table animal (id serial not null primary key, species varchar(256), name varchar(256), age integer)" do-command
-    "insert into animal (species, name, age) values ('lion', 'Mufasa', 5)"
-    do-command
-
-    "select * from animal where name = 'Mufasa'" [ ] do-query
-    "select * from animal where name = 'Mufasa'"
-    [
-        result>seq length 1 = [ "...there can only be one Mufasa..." throw ] unless
-    ] do-query
-
-    "insert into animal (species, name, age) values ('lion', 'Simba', 1)"
-    do-command
-
-    "select * from animal" 
-    [
-          "Animal table:" print
-          result>seq print-table
-    ] do-query
-
-    ! intentional errors
-    ! [ "select asdf from animal"
-    ! [ ] do-query ] catch [ "caught: " write print ] when*
-    ! "select asdf from animal" [ ] do-query 
-    ! "aofijweafew" do-command
-] with-postgres
-
diff --git a/extra/postgresql/postgresql.factor b/extra/postgresql/postgresql.factor
deleted file mode 100644
index 9d85b6a77e..0000000000
--- a/extra/postgresql/postgresql.factor
+++ /dev/null
@@ -1,61 +0,0 @@
-! Copyright (C) 2007 Doug Coleman.
-! See http://factorcode.org/license.txt for BSD license.
-
-! adapted from libpq-fe.h version 7.4.7
-! tested on debian linux with postgresql 7.4.7
-
-USING: arrays alien alien.syntax continuations io
-kernel math namespaces postgresql.libpq prettyprint
-quotations sequences debugger ;
-IN: postgresql
-
-SYMBOL: db
-SYMBOL: query-res
-
-: connect-postgres ( host port pgopts pgtty db user pass -- conn )
-    PQsetdbLogin
-    dup PQstatus zero? [ "couldn't connect to database" throw ] unless ;
-
-: with-postgres ( host port pgopts pgtty db user pass quot -- )
-    [ >r connect-postgres db set r>
-    [ db get PQfinish ] [ ] cleanup ] with-scope ; inline
-
-: postgres-error ( ret -- ret )
-    dup zero? [ PQresultErrorMessage throw ] when ;
-
-: (do-query) ( PGconn query -- PGresult* )
-    ! For queries that do not return rows, PQexec() returns PGRES_COMMAND_OK
-    ! For queries that return rows, PQexec() returns PGRES_TUPLES_OK
-    PQexec
-    dup PQresultStatus PGRES_COMMAND_OK =
-    over PQresultStatus PGRES_TUPLES_OK =
-    or [
-        [ PQresultErrorMessage CHAR: \n swap remove ] keep PQclear throw
-    ] unless ;
-
-: (do-command) ( PGconn query -- PGresult* )
-    [ (do-query) ] catch
-    [
-        swap
-        "non-fatal error: " print
-        "\tQuery: " write "'" write write "'" print
-        "\t" write print
-    ] when* drop ;
-
-: do-command ( str -- )
-    1quotation \ (do-command) add db get swap call ;
-
-: prepare ( str quot word -- conn quot )
-    rot 1quotation swap append swap append db get swap ;
-
-: do-query ( str quot -- )
-    [ (do-query) query-res set ] prepare catch
-    [ rethrow ] [ query-res get PQclear ] if* ;
-
-: result>seq ( -- seq )
-    query-res get [ PQnfields ] keep PQntuples
-    [ swap [ query-res get -rot PQgetvalue ] with map ] with map ;
-
-: print-table ( seq -- )
-    [ [ write bl ] each "\n" write ] each ;
-
diff --git a/extra/project-euler/032/032.factor b/extra/project-euler/032/032.factor
index d10326a076..2baa6f8714 100644
--- a/extra/project-euler/032/032.factor
+++ b/extra/project-euler/032/032.factor
@@ -1,7 +1,7 @@
 ! Copyright (c) 2008 Aaron Schaefer.
 ! See http://factorcode.org/license.txt for BSD license.
 USING: combinators.lib hashtables kernel math math.combinatorics math.parser
-    math.ranges project-euler.common sequences sorting ;
+    math.ranges project-euler.common sequences ;
 IN: project-euler.032
 
 ! http://projecteuler.net/index.php?section=problems&id=32
@@ -63,9 +63,6 @@ PRIVATE>
 : source-032a ( -- seq )
     50 [1,b] 2000 [1,b] cartesian-product ;
 
-: pandigital? ( n -- ? )
-    number>string natural-sort "123456789" = ;
-
 ! multiplicand/multiplier/product
 : mmp ( pair -- n )
     first2 2dup * [ number>string ] 3apply 3append 10 string>integer ;
diff --git a/extra/project-euler/037/037.factor b/extra/project-euler/037/037.factor
new file mode 100644
index 0000000000..f2d5d17c4d
--- /dev/null
+++ b/extra/project-euler/037/037.factor
@@ -0,0 +1,52 @@
+! Copyright (c) 2008 Aaron Schaefer.
+! See http://factorcode.org/license.txt for BSD license.
+USING: kernel math math.parser math.primes sequences ;
+IN: project-euler.037
+
+! http://projecteuler.net/index.php?section=problems&id=37
+
+! DESCRIPTION
+! -----------
+
+! The number 3797 has an interesting property. Being prime itself, it is
+! possible to continuously remove digits from left to right, and remain prime
+! at each stage: 3797, 797, 97, and 7. Similarly we can work from right to
+! left: 3797, 379, 37, and 3.
+
+! Find the sum of the only eleven primes that are both truncatable from left to
+! right and right to left.
+
+! NOTE: 2, 3, 5, and 7 are not considered to be truncatable primes.
+
+
+! SOLUTION
+! --------
+
+<PRIVATE
+
+: r-trunc? ( n -- ? )
+    10 /i dup 0 > [
+        dup prime? [ r-trunc? ] [ drop f ] if
+    ] [
+        drop t
+    ] if ;
+
+: reverse-digits ( n -- m )
+    number>string reverse 10 string>integer ;
+
+: l-trunc? ( n -- ? )
+    reverse-digits 10 /i reverse-digits dup 0 > [
+        dup prime? [ l-trunc? ] [ drop f ] if
+    ] [
+        drop t
+    ] if ;
+
+PRIVATE>
+
+: euler037 ( -- answer )
+    23 1000000 primes-between [ r-trunc? ] subset [ l-trunc? ] subset sum ;
+
+! [ euler037 ] 100 ave-time
+! 768 ms run / 9 ms GC ave time - 100 trials
+
+MAIN: euler037
diff --git a/extra/project-euler/038/038.factor b/extra/project-euler/038/038.factor
new file mode 100644
index 0000000000..cbe6f2363c
--- /dev/null
+++ b/extra/project-euler/038/038.factor
@@ -0,0 +1,55 @@
+! Copyright (c) 2008 Aaron Schaefer.
+! See http://factorcode.org/license.txt for BSD license.
+USING: kernel math math.parser math.ranges project-euler.common sequences ;
+IN: project-euler.038
+
+! http://projecteuler.net/index.php?section=problems&id=38
+
+! DESCRIPTION
+! -----------
+
+! Take the number 192 and multiply it by each of 1, 2, and 3:
+
+!     192 × 1 = 192
+!     192 × 2 = 384
+!     192 × 3 = 576
+
+! By concatenating each product we get the 1 to 9 pandigital, 192384576. We
+! will call 192384576 the concatenated product of 192 and (1,2,3)
+
+! The same can be achieved by starting with 9 and multiplying by 1, 2, 3, 4,
+! and 5, giving the pandigital, 918273645, which is the concatenated product of
+! 9 and (1,2,3,4,5).
+
+! What is the largest 1 to 9 pandigital 9-digit number that can be formed as
+! the concatenated product of an integer with (1,2, ... , n) where n > 1?
+
+
+! SOLUTION
+! --------
+
+! Only need to search 4-digit numbers starting with 9 since a 2-digit number
+! starting with 9 would produce 8 or 11 digits, and a 3-digit number starting
+! with 9 would produce 7 or 11 digits.
+
+<PRIVATE
+
+: (concat-product) ( accum n multiplier -- m )
+    pick length 8 > [
+        2drop 10 swap digits>integer
+    ] [
+        [ * number>digits over push-all ] 2keep 1+ (concat-product)
+    ] if ;
+
+: concat-product ( n -- m )
+    V{ } clone swap 1 (concat-product) ;
+
+PRIVATE>
+
+: euler038 ( -- answer )
+    9123 9876 [a,b] [ concat-product ] map [ pandigital? ] subset supremum ;
+
+! [ euler038 ] 100 ave-time
+! 37 ms run / 1 ms GC ave time - 100 trials
+
+MAIN: euler038
diff --git a/extra/project-euler/039/039.factor b/extra/project-euler/039/039.factor
new file mode 100644
index 0000000000..67578dc5f2
--- /dev/null
+++ b/extra/project-euler/039/039.factor
@@ -0,0 +1,65 @@
+! Copyright (c) 2008 Aaron Schaefer.
+! See http://factorcode.org/license.txt for BSD license.
+USING: arrays combinators.lib kernel math math.ranges namespaces
+    project-euler.common sequences ;
+IN: project-euler.039
+
+! http://projecteuler.net/index.php?section=problems&id=39
+
+! DESCRIPTION
+! -----------
+
+! If p is the perimeter of a right angle triangle with integral length sides,
+! {a,b,c}, there are exactly three solutions for p = 120.
+
+!     {20,48,52}, {24,45,51}, {30,40,50}
+
+! For which value of p < 1000, is the number of solutions maximised?
+
+
+! SOLUTION
+! --------
+
+! Algorithm adapted from http://mathworld.wolfram.com/PythagoreanTriple.html
+! Identical implementation as problem #75
+
+! Basically, this makes an array of 1000 zeros, recursively creates primitive
+! triples using the three transforms and then increments the array at index
+! [a+b+c] by one for each triple's sum AND its multiples under 1000 (to account
+! for non-primitive triples). The answer is just the index that has the highest
+! number.
+
+SYMBOL: p-count
+
+<PRIVATE
+
+: max-p ( -- n )
+    p-count get length ;
+
+: adjust-p-count ( n -- )
+    max-p 1- over <range> p-count get
+    [ [ 1+ ] change-nth ] curry each ;
+
+: (count-perimeters) ( seq -- )
+    dup sum max-p < [
+        dup sum adjust-p-count
+        [ u-transform ] keep [ a-transform ] keep d-transform
+        [ (count-perimeters) ] 3apply
+    ] [
+        drop
+    ] if ;
+
+: count-perimeters ( n -- )
+    0 <array> p-count set { 3 4 5 } (count-perimeters) ;
+
+PRIVATE>
+
+: euler039 ( -- answer )
+    [
+        1000 count-perimeters p-count get [ supremum ] keep index
+    ] with-scope ;
+
+! [ euler039 ] 100 ave-time
+! 2 ms run / 0 ms GC ave time - 100 trials
+
+MAIN: euler039
diff --git a/extra/project-euler/040/040.factor b/extra/project-euler/040/040.factor
new file mode 100644
index 0000000000..8984559265
--- /dev/null
+++ b/extra/project-euler/040/040.factor
@@ -0,0 +1,51 @@
+! Copyright (c) 2008 Aaron Schaefer.
+! See http://factorcode.org/license.txt for BSD license.
+USING: kernel math math.parser sequences strings ;
+IN: project-euler.040
+
+! http://projecteuler.net/index.php?section=problems&id=40
+
+! DESCRIPTION
+! -----------
+
+! An irrational decimal fraction is created by concatenating the positive
+! integers:
+
+!     0.123456789101112131415161718192021...
+
+! It can be seen that the 12th digit of the fractional part is 1.
+
+! If dn represents the nth digit of the fractional part, find the value of the
+! following expression.
+
+!     d1 × d10 × d100 × d1000 × d10000 × d100000 × d1000000
+
+
+! SOLUTION
+! --------
+
+<PRIVATE
+
+: (concat-upto) ( n limit str -- str )
+    2dup length > [
+        pick number>string over push-all rot 1+ -rot (concat-upto)
+    ] [
+        2nip
+    ] if ;
+
+: concat-upto ( n -- str )
+    SBUF" " clone 1 -rot (concat-upto) ;
+
+: nth-integer ( n str -- m )
+    [ 1- ] dip nth 1string 10 string>integer ;
+
+PRIVATE>
+
+: euler040 ( -- answer )
+    1000000 concat-upto { 1 10 100 1000 10000 100000 1000000 }
+    [ swap nth-integer ] with map product ;
+
+! [ euler040 ] 100 ave-time
+! 1002 ms run / 43 ms GC ave time - 100 trials
+
+MAIN: euler040
diff --git a/extra/project-euler/075/075.factor b/extra/project-euler/075/075.factor
new file mode 100644
index 0000000000..8399235c0d
--- /dev/null
+++ b/extra/project-euler/075/075.factor
@@ -0,0 +1,78 @@
+! Copyright (c) 2008 Aaron Schaefer.
+! See http://factorcode.org/license.txt for BSD license.
+USING: arrays combinators.lib kernel math math.ranges namespaces
+    project-euler.common sequences ;
+IN: project-euler.075
+
+! http://projecteuler.net/index.php?section=problems&id=75
+
+! DESCRIPTION
+! -----------
+
+! It turns out that 12 cm is the smallest length of wire can be bent to form a
+! right angle triangle in exactly one way, but there are many more examples.
+
+!     12 cm: (3,4,5)
+!     24 cm: (6,8,10)
+!     30 cm: (5,12,13)
+!     36 cm: (9,12,15)
+!     40 cm: (8,15,17)
+!     48 cm: (12,16,20)
+
+! In contrast, some lengths of wire, like 20 cm, cannot be bent to form a right
+! angle triangle, and other lengths allow more than one solution to be found;
+! for example, using 120 cm it is possible to form exactly three different
+! right angle triangles.
+
+!     120 cm: (30,40,50), (20,48,52), (24,45,51)
+
+! Given that L is the length of the wire, for how many values of L ≤ 1,000,000
+! can exactly one right angle triangle be formed?
+
+
+! SOLUTION
+! --------
+
+! Algorithm adapted from http://mathworld.wolfram.com/PythagoreanTriple.html
+! Identical implementation as problem #39
+
+! Basically, this makes an array of 1000000 zeros, recursively creates
+! primitive triples using the three transforms and then increments the array at
+! index [a+b+c] by one for each triple's sum AND its multiples under 1000000
+! (to account for non-primitive triples). The answer is just the total number
+! of indexes that are equal to one.
+
+SYMBOL: p-count
+
+<PRIVATE
+
+: max-p ( -- n )
+    p-count get length ;
+
+: adjust-p-count ( n -- )
+    max-p 1- over <range> p-count get
+    [ [ 1+ ] change-nth ] curry each ;
+
+: (count-perimeters) ( seq -- )
+    dup sum max-p < [
+        dup sum adjust-p-count
+        [ u-transform ] keep [ a-transform ] keep d-transform
+        [ (count-perimeters) ] 3apply
+    ] [
+        drop
+    ] if ;
+
+: count-perimeters ( n -- )
+    0 <array> p-count set { 3 4 5 } (count-perimeters) ;
+
+PRIVATE>
+
+: euler075 ( -- answer )
+    [
+        1000000 count-perimeters p-count get [ 1 = ] count
+    ] with-scope ;
+
+! [ euler075 ] 100 ave-time
+! 1873 ms run / 123 ms GC ave time - 100 trials
+
+MAIN: euler075
diff --git a/extra/project-euler/common/common.factor b/extra/project-euler/common/common.factor
index 2e718ab5a2..50adbe4953 100644
--- a/extra/project-euler/common/common.factor
+++ b/extra/project-euler/common/common.factor
@@ -1,5 +1,6 @@
 USING: arrays combinators.lib kernel math math.functions math.miller-rabin
-    math.parser math.primes.factors math.ranges namespaces sequences ;
+    math.matrices math.parser math.primes.factors math.ranges namespaces
+    sequences sorting ;
 IN: project-euler.common
 
 ! A collection of words used by more than one Project Euler solution
@@ -12,9 +13,11 @@ IN: project-euler.common
 ! log10 - #25, #134
 ! max-path - #18, #67
 ! number>digits - #16, #20, #30, #34
+! pandigital? - #32, #38
 ! propagate-all - #18, #67
 ! sum-proper-divisors - #21
 ! tau* - #12
+! [uad]-transform - #39, #75
 
 
 : nth-pair ( n seq -- nth next )
@@ -44,6 +47,9 @@ IN: project-euler.common
         dup perfect-square? [ sqrt >fixnum neg , ] [ drop ] if
     ] { } make sum ;
 
+: transform ( triple matrix -- new-triple )
+    [ 1array ] dip m. first ;
+
 PRIVATE>
 
 : cartesian-product ( seq1 seq2 -- seq1xseq2 )
@@ -67,6 +73,9 @@ PRIVATE>
 : number>digits ( n -- seq )
     number>string string>digits ;
 
+: pandigital? ( n -- ? )
+    number>string natural-sort "123456789" = ;
+
 ! Not strictly needed, but it is nice to be able to dump the triangle after the
 ! propagation
 : propagate-all ( triangle -- newtriangle )
@@ -97,3 +106,12 @@ PRIVATE>
     dup sqrt >fixnum [1,b] [
         dupd mod zero? [ [ 2 + ] dip ] when
     ] each drop * ;
+
+! These transforms are for generating primitive Pythagorean triples
+: u-transform ( triple -- new-triple )
+    { { 1 2 2 } { -2 -1 -2 } { 2 2 3 } } transform ;
+: a-transform ( triple -- new-triple )
+    { { 1 2 2 } { 2 1 2 } { 2 2 3 } } transform ;
+: d-transform ( triple -- new-triple )
+    { { -1 -2 -2 } { 2 1 2 } { 2 2 3 } } transform ;
+
diff --git a/extra/project-euler/project-euler.factor b/extra/project-euler/project-euler.factor
index feef9dbfa8..eb9d7d1300 100644
--- a/extra/project-euler/project-euler.factor
+++ b/extra/project-euler/project-euler.factor
@@ -1,7 +1,7 @@
 ! Copyright (c) 2007, 2008 Aaron Schaefer, Samuel Tardieu.
 ! See http://factorcode.org/license.txt for BSD license.
-USING: definitions io io.files kernel math.parser sequences vocabs
-    vocabs.loader project-euler.ave-time project-euler.common math
+USING: definitions io io.files kernel math math.parser project-euler.ave-time
+    sequences vocabs vocabs.loader
     project-euler.001 project-euler.002 project-euler.003 project-euler.004
     project-euler.005 project-euler.006 project-euler.007 project-euler.008
     project-euler.009 project-euler.010 project-euler.011 project-euler.012
@@ -11,8 +11,9 @@ USING: definitions io io.files kernel math.parser sequences vocabs
     project-euler.025 project-euler.026 project-euler.027 project-euler.028
     project-euler.029 project-euler.030 project-euler.031 project-euler.032
     project-euler.033 project-euler.034 project-euler.035 project-euler.036
-    project-euler.067 project-euler.134 project-euler.169 project-euler.173
-    project-euler.175 ;
+    project-euler.037 project-euler.038 project-euler.039 project-euler.040
+    project-euler.067 project-euler.075 project-euler.134 project-euler.169
+    project-euler.173 project-euler.175 ;
 IN: project-euler
 
 <PRIVATE
diff --git a/extra/sequences/lib/lib.factor b/extra/sequences/lib/lib.factor
index 65b0d1beb0..d89c5eec89 100755
--- a/extra/sequences/lib/lib.factor
+++ b/extra/sequences/lib/lib.factor
@@ -141,6 +141,9 @@ PRIVATE>
 : ?third ( seq -- third/f ) 2 swap ?nth ; inline
 : ?fourth ( seq -- fourth/f ) 3 swap ?nth ; inline
 
+: accumulator ( quot -- quot vec )
+    V{ } clone [ [ push ] curry compose ] keep ;
+
 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
 ! List the positions of obj in seq
diff --git a/extra/unicode/case/case.factor b/extra/unicode/case/case.factor
index ee9e2a0381..8129ec17f8 100755
--- a/extra/unicode/case/case.factor
+++ b/extra/unicode/case/case.factor
@@ -1,6 +1,6 @@
 USING: kernel unicode.data sequences sequences.next namespaces
 assocs.lib unicode.normalize math unicode.categories combinators
-assocs ;
+assocs strings splitting ;
 IN: unicode.case
 
 : ch>lower ( ch -- lower ) simple-lower at-default ;
diff --git a/misc/Factor.tmbundle/Support/lib/tm_factor.rb b/misc/Factor.tmbundle/Support/lib/tm_factor.rb
index 54272e5e36..2775a12ae9 100644
--- a/misc/Factor.tmbundle/Support/lib/tm_factor.rb
+++ b/misc/Factor.tmbundle/Support/lib/tm_factor.rb
@@ -33,6 +33,6 @@ def doc_using_statements(document)
 end
 
 def line_current_word(line, point)
-    left = line.rindex(/\s|^/, point - 1) + 1; right = line.index(/\s|$/, point) - 1
+    left = line.rindex(/\s/, point - 1) || 0; right = line.index(/\s/, point) || line.length
     line[left..right]
 end
diff --git a/misc/factor.sh b/misc/factor.sh
index 39a15f93dc..032b0b3184 100755
--- a/misc/factor.sh
+++ b/misc/factor.sh
@@ -289,7 +289,7 @@ install_libraries() {
 }
 
 usage() {
-        echo "usage: $0 install|install-x11|self-update|quick-update|update|bootstrap"
+        echo "usage: $0 install|install-x11|self-update|quick-update|update|bootstrap|wget-bootstrap"
 }
 
 case "$1" in
@@ -299,5 +299,6 @@ case "$1" in
         quick-update) update; refresh_image ;;
         update) update; update_bootstrap ;;
         bootstrap) get_config_info; bootstrap ;;
+        wget-bootstrap) get_config_info; delete_boot_images; get_boot_image; bootstrap ;;
         *) usage ;;
 esac