]> gitweb.factorcode.org Git - factor.git/commitdiff
Changes to cont-responder:
authorChris Double <chris.double@double.co.nz>
Sat, 20 Nov 2004 21:32:02 +0000 (21:32 +0000)
committerChris Double <chris.double@double.co.nz>
Sat, 20 Nov 2004 21:32:02 +0000 (21:32 +0000)
  added start of a tutorial
  added numbers game example
  add cont-responder testing functions
  minor refactoring of cont-responder
  eval-responder now works again!

Changes to parser-combinators:
 modify str-head and str-tail usage for their changed stack effects

contrib/cont-responder/cont-numbers-game.factor [new file with mode: 0644]
contrib/cont-responder/cont-responder.factor
contrib/cont-responder/cont-testing.factor [new file with mode: 0644]
contrib/cont-responder/eval-responder.factor
contrib/cont-responder/load.factor
contrib/cont-responder/tutorial.txt [new file with mode: 0644]
contrib/parser-combinators/parser-combinators.factor

diff --git a/contrib/cont-responder/cont-numbers-game.factor b/contrib/cont-responder/cont-numbers-game.factor
new file mode 100644 (file)
index 0000000..1e79e07
--- /dev/null
@@ -0,0 +1,103 @@
+! cont-number-guess
+!
+! Copyright (C) 2004 Chris Double.
+! 
+! Redistribution and use in source and binary forms, with or without
+! modification, are permitted provided that the following conditions are met:
+! 
+! 1. Redistributions of source code must retain the above copyright notice,
+!    this list of conditions and the following disclaimer.
+! 
+! 2. Redistributions in binary form must reproduce the above copyright notice,
+!    this list of conditions and the following disclaimer in the documentation
+!    and/or other materials provided with the distribution.
+! 
+! THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+! INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+! FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+! DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+! SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+! PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+! OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+! WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+! OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+! ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+!
+! This example modifies the console based 'numbers-game' example
+! in a very minimal way to demonstrate conversion of a console
+! program to a web based application.
+!
+! All that was required was changing the input and output functions
+! to use HTML. The remaining code was untouched. 
+!
+! The result is not that pretty but it shows the basic idea.
+IN: numbers-game
+USE: combinators
+USE: kernel
+USE: math
+USE: random
+USE: parser
+USE: html
+USE: cont-responder
+USE: cont-utils
+USE: stack
+USE: stdio
+USE: namespaces
+
+: web-print ( str -- )
+  #! Display the string in a web page.
+  [
+    swap dup
+    <html>
+      <head> <title> write </title> </head>
+      <body>
+        <p> write </p>
+        <p> <a href= a> "Press to continue" write </a> </p>
+      </body>
+    </html>
+  ] show 2drop ;
+
+: read-number ( -- )
+  [
+    <html>
+      <head> <title> "Enter a number" write </title> </head>
+      <body>
+        <form action= method= "post" form>
+          <p> 
+            "Enter a number:" write
+            <input type= "text" name= "num" size= "20" input/>
+            <input type= "submit" value= "Press to continue" input/>
+          </p>
+        </form>
+      </body>
+    </html>
+  ] show [ "num" get ] bind parse-number ;
+
+: guess-banner
+  "I'm thinking of a number between 0 and 100." web-print ;
+: guess-prompt "Enter your guess: " web-print ;
+: too-high "Too high" web-print ;
+: too-low "Too low" web-print ;
+: correct "Correct - you win!" web-print ;
+: inexact-guess ( actual guess -- )
+     < [ too-high ] [ too-low ] ifte ;
+
+: judge-guess ( actual guess -- ? )
+    2dup = [
+        2drop correct f
+    ] [
+        inexact-guess t
+    ] ifte ;
+
+: number-to-guess ( -- n ) 0 100 random-int ;
+
+: numbers-game-loop ( actual -- )
+    dup guess-prompt read-number judge-guess [
+        numbers-game-loop
+    ] [
+        drop
+    ] ifte ;
+
+: numbers-game number-to-guess numbers-game-loop ;
+
+"numbers-game" [ numbers-game ] install-cont-responder
\ No newline at end of file
index 19f1b134edea83a817c6cc6f4e84a3f6180e7e8d..540cae6a1f0b05ad8bebc5be47c07329f06a3021 100644 (file)
@@ -44,6 +44,9 @@ USE: url-encoding
 USE: unparser
 USE: hashtables
 
+USE: prettyprint
+USE: inspector
+
 : expiry-timeout ( -- timeout-seconds )
   #! Number of seconds to timeout continuations in
   #! continuation table. This value will need to be
@@ -194,9 +197,7 @@ DEFER: show
 : with-string-stream ( quot -- string ) 
   #! Call the quotation with standard output bound to a string output
   #! stream. Return the string on exit.
-  <namespace> [ 
-    "stdio" 1024 <string-output-stream> put call "stdio" get stream>str 
-  ] bind ;
+  1024 <string-output-stream> dup >r swap with-stream r> stream>str ;
 
 : redirect-to-here ( -- )
   #! Force a redirect to the client browser so that the browser
@@ -234,8 +235,7 @@ DEFER: show
     call-exit-continuation 
   ] callcc1 
   nip ;
-USE: prettyprint
-USE: inspector
+
 
 : cont-get-responder ( id-or-f -- ) 
   #! httpd responder that retrieves a continuation and calls it.
@@ -254,17 +254,12 @@ USE: inspector
   ] ifte 
   [ write flush ] when* drop ;
 
-: post-request>namespace ( post-request -- namespace )
-  #! Return a namespace containing the name/value's from the 
-  #! post data.
-  alist>hash ;
-
 : cont-post-responder ( id -- )    
   #! httpd responder that retrieves a continuation for the given
-  #! id and calls it with the POST data as an alist on the top
+  #! id and calls it with the POST data as a hashtable on the top
   #! of the stack.
   [ 
-    "response" get post-request>namespace swap resume-continuation 
+    "response" get alist>hash swap resume-continuation 
   ] with-exit-continuation
   print drop ;
 
diff --git a/contrib/cont-responder/cont-testing.factor b/contrib/cont-responder/cont-testing.factor
new file mode 100644 (file)
index 0000000..a9b4f16
--- /dev/null
@@ -0,0 +1,114 @@
+! cont-testing
+!
+! Copyright (C) 2004 Chris Double.
+! 
+! Redistribution and use in source and binary forms, with or without
+! modification, are permitted provided that the following conditions are met:
+! 
+! 1. Redistributions of source code must retain the above copyright notice,
+!    this list of conditions and the following disclaimer.
+! 
+! 2. Redistributions in binary form must reproduce the above copyright notice,
+!    this list of conditions and the following disclaimer in the documentation
+!    and/or other materials provided with the distribution.
+! 
+! THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+! INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+! FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+! DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+! SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+! PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+! OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+! WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+! OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+! ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+!
+! Words for testing continuation based responders at the console
+! prompt.
+!
+! To start a 'test' session use '<cont-test-state>' to push the
+! continuation responder state on the stack.
+!
+! Then use 'test-cont-function' to call a continuation responder word.
+! All output will go to the console. From this output you will see
+! links that you can 'visit' by doing a simulated click. Use the
+! 'test-cont-click' function by passing the state, the 'id' of the click
+! continuation, and 'f' or a hashtable containing the post data. The output
+! from this will be displayed.
+!
+! eg.
+! <cont-test-state> [ test-cont-responder ] test-cont-function
+!   => HTTP/1.1 302 Document Moved
+!      Location: 8506502852110820
+!      Content-Length: 0
+!      Content-Type: text/plain
+!
+! "8506502852110820" f test-cont-click
+!   => HTTP/1.0 200 Document follows
+!      Content-Type: text/html
+!
+!      <html><head><title>Page one</title></head><body>
+!      <h1>Page one</h1><a href='5431597582800278'>Next</a>
+!      </body></html>
+!
+! "5431597582800278" f test-cont-click
+!   => HTTP/1.1 302 Document Moved
+!      Location: 7944183606904129
+!      Content-Length: 0
+!      Content-Type: text/plain
+!
+! "7944183606904129" f test-cont-click
+!   => HTTP/1.0 200 Document follows
+!      Content-Type: text/html
+!
+!      <html><head><title>Enter your name</title></head>
+!      <body><h1>Enter your name</h1>
+!      <form method='post'action='8503790719833723'>
+!      Name: <input type='text'name='name'size='20'>
+!      <input type='submit'value='Ok'>
+!      </form></body></html>
+!
+! "8503790719833723" [ [ "name" | "Chris" ] ] alist>hash test-cont-click
+!   => HTTP/1.1 302 Document Moved
+!      Location: 8879727708050260
+!      Content-Length: 0
+!      Content-Type: text/plain
+!
+! "8879727708050260" f test-cont-click
+!   => HTTP/1.0 200 Document follows
+!      Content-Type: text/html
+!
+!      <html><head><title>Hello Chris</title></head>
+!      <body><h1>Hello Chris</h1>
+!      <a href='0937854264503953'>Next</a>
+!      </body></html>
+! 
+! etc.
+IN: cont-responder
+USE: namespaces
+USE: stack
+USE: combinators
+USE: stdio
+
+: <cont-test-state> ( -- <state> )
+  #! Create a namespace holding data required
+  #! for testing continuation based responder functions
+  #! at the interpreter console.
+  <namespace> [
+    reset-continuation-table
+    init-session-namespace    
+  ] extend ;
+
+: test-cont-function ( <state> quot -- <state> )
+  #! Call a continuation responder function with required
+  #! plumbing set up so output is displayed to the console.
+  swap dup >r [
+    [ call ] with-exit-continuation
+  ] bind write drop r> ;
+
+: test-cont-click ( <state> id data -- <state> )
+  #! Test function to 'click' a continuation with the given
+  #! 'id' and post data. Display the results on the console.
+  rot dup >r [
+    [ swap resume-continuation ] with-exit-continuation 
+  ] bind write 2drop r> ;
index 8b25706ef04a042461c94db057ceec3349054a19..4cc4af7c640f54e3d3d47842a6d781370d62fefc 100644 (file)
@@ -39,7 +39,10 @@ USE: logic
 USE: combinators
 USE: live-updater
 USE: prettyprint
+USE: unparser
 USE: words
+USE: vectors
+USE: logging
 
 : <evaluator> ( stack msg history -- )
   #! Create an 'evaluator' object that holds
@@ -71,13 +74,12 @@ USE: words
 : escape-quotes ( string -- string )
   #! Replace occurrences of single quotes with
   #! backslash quote.
-  [ dup [ [ "'" | "\\'" ] [ "\"" | "\\\"" ] ] assoc dup rot ? ] str-map ;
+  [ dup [ [ CHAR: ' | "\\'" ] [ CHAR: " | "\\\"" ] ] assoc dup rot ? ] str-map ;
  
 : make-eval-javascript ( string -- string )
   #! Give a string return some javascript that when
   #! executed will set the eval textarea to that string.
   [ "document.forms.main.eval.value=\"" , escape-quotes , "\"" , ] make-string ;
-
 : write-eval-link ( string -- )
   #! Given text to evaluate, create an A HREF link which when
   #! clicked sets the eval textarea to that value.
@@ -87,7 +89,7 @@ USE: words
   #! Write out html to display the stack.
   <table border= "1" table> 
     <tr> <th> "Callstack" write </th> </tr>
-    [ <tr> <td> write-eval-link </td> </tr> ] each
+    [ <tr> <td> [ unparse write ] with-string-stream write-eval-link </td> </tr> ] each
   </table> ;
 
 : display-clear-history-link ( -- )
@@ -112,7 +114,7 @@ USE: words
     "responder" "inspect" put
     <table border= "1" table> 
       <tr> <th colspan= "2" th> "Source" write </th> </tr>
-      <tr> <td colspan= "2" td> [ see ] with-simple-html-output </td> </tr>
+      <tr> <td colspan= "2" td> [ [ parse ] [ [ "No such word" write ] [ car see ] ifte ] catch ] with-simple-html-output </td> </tr>
       <tr> <th> "Apropos" write </th> <th> "Usages" write </th> </tr>
       <tr> <td valign= "top" td> [ apropos. ] with-simple-html-output </td> 
            <td valign= "top" td> [ usages. ] with-simple-html-output </td>
@@ -169,10 +171,18 @@ USE: words
     "eval" get
   ] bind ;
    
+: infra ( list quot -- list )
+  #! Call the quotation using 'list' as the datastack
+  #! return the result datastack as a list.
+  datastack >r    
+  swap list>vector tuck vector-push 
+  set-datastack call datastack vector>list
+  r> >pop> >pop> tuck vector-push set-datastack ;
+
 : do-eval ( list string -- list )
   #! Evaluate the expression in 'string' using 'list' as
   #! the datastack. Return the resulting stack as a list.
-  parse unit append restack call unstack ;
+  parse infra ;
 
 : do-eval-to-string ( list string -- list string )
   #! Evaluate expression using 'list' as the current callstack.
@@ -202,8 +212,9 @@ USE: words
     [ 
       run-eval-requester 
     ] [
-      show-message-page
+      dup [ show-message-page ] [ drop ] ifte
     ] catch 
   ] forever ;
 
 "eval" [ [ ] "None" [ ] <evaluator> eval-responder ] install-cont-responder
+
index a8a6e302e2df2e1b6b6404e0734bbacbb3e85462..fcb1503e8acc7fa0b723db1de1060def56025f07 100644 (file)
@@ -38,13 +38,15 @@ USE: parser
   "cont-responder.factor" run-file 
   "cont-utils.factor" run-file ;
 : l2 
-  "cont-examples.factor" run-file ;
+  "cont-examples.factor" run-file 
+  "cont-numbers-game.factor" run-file ;
 : l3 "todo.factor" run-file ;
 : l4 "todo-example.factor" run-file ;
 : l5 "live-updater.factor" run-file ;
-: l6 "eval-responder.factor" run-file ;
+: l6 "eval-responder.factor" run-file ;
 : l7 "live-updater-responder.factor" run-file ;
 : l8 "browser.factor" run-file ;
+: l9 "cont-testing.factor" run-file ;
 : la ;
 : la [ 8888 httpd ] [ dup . flush [ la ] when* ] catch ;
 : lb [ la "httpd thread exited.\n" write flush ] in-thread  ;
diff --git a/contrib/cont-responder/tutorial.txt b/contrib/cont-responder/tutorial.txt
new file mode 100644 (file)
index 0000000..e38d67f
--- /dev/null
@@ -0,0 +1,407 @@
+Overview
+========
+
+The 'cont-responder' library is a continuation based web server
+for writing web applications in Factor. Each 'web application' is a
+standard Factor httpd responder.
+
+This document outlines how to write simple web applications using
+'cont-responder' by showing examples. It does not attempt to go into
+the technical details of continuation based web applications or how it
+is implemented in Factor. Instead it uses a series of examples that
+can be immediately tried at the Factor prompt to get a feel for how
+things work.
+
+Getting Started
+===============
+To get started you will first need to load the 'cont-responder'
+code. You will need the following as a minimum:
+
+  "cont-responder.factor" run-file
+  "cont-utils.factor" run-file
+  USE: cont-responder
+  USE: cont-utils
+
+The responders that you will be writing will require an instance of
+the httpd server to be running. It will be run in a background thread
+to enable the interactive development of the applications. The
+following is a simple function to start the server on port 8888 and
+restart it if an error occurs:
+
+  USE: httpd
+  USE: threads
+  : start-httpd [ 8888 httpd ] [ dup . flush [ start-httpd ] when* ] catch ;
+  [ start-httpd ] in-thread
+
+Responders
+==========
+A 'responder' is a word that is registered with the httpd server that
+gets run when the client accesses a particular URL. When run that word
+has 'standard output' bound in such a way that all output goes to the
+clients web browser. 
+
+In the 'cont-responder' system the word used to set output to go to the web
+browser and display a page is 'show'. Think of it as 'show a page to
+the client'. 'show' takes a single item on the stack and that is a
+'page generation' quotation. 
+
+A 'page generation' quotation is a quotation with stack effect (
+string -- ). For now we'll ignore the string it receives on the
+stack. Its purpose will be explained later.
+
+Hello World 1
+=============
+A simple 'hello world' responder would be:
+
+  : hello-world1 ( -- )
+    [
+      drop
+      "<html><head><title>Hello World</title></head>" write
+      "<body>Hello World!</body></html>" write
+    ] show drop ;
+
+When installed this will show a single page which is simple HTML to
+display 'Hello World!'. The 'show' word returns a namespace, the
+purpose of which will also be explained later. For now we ignore it
+and drop it. Notice we also drop the 'URL' that the quotation passed
+to 'show' receives on the stack.
+
+The responder is installed using:
+
+  "helloworld1" [ hello-world1 ] install-cont-responder
+
+The 'install-cont-responder' word has stack effect ( name quot --
+). It installs a responder with the given name. When the URL for that
+responder is accessed the 'quot' quotation is run. In this case it is
+'hello-world1' which displays the single HTML page described
+previously.
+
+Accessing the above responder from a web browser is via an URL like:
+
+  http://localhost:8888/responder/helloworld1
+
+This should display an HTML page showing 'Hello World!".
+
+HTML Generation
+===============
+Generating HTML by writing strings containing HTML can be a bit of a
+chore. Especially when the content is dynamic requiring concatenation
+of many pieces of data. 
+
+The 'cont-responder' system uses 'html', a library that allows writing
+HTML looking output directly in factor. This system developed for
+'cont-responder' has recently been made part of the standard 'html'
+library of Factor.
+
+'html' basically allows you to write HTML-like output in a factor word
+and it will be automatically output. It can be tested at the console
+very easily:
+
+  USE: html
+  <p> "This is a paragraph" write </p>
+    => <p>This is a paragraph</p>
+
+You can write open and close tags like orginary HTML and anything sent
+to standard output in between the tags will be enclosed in the
+specified tags. Attributes can also be used:
+
+  <p style= "text-align: center" p> "More text" write </p>
+    => <p style='text-align: center'>More text</p>
+
+The attribute must be seperated from the value of that attribute via
+whitespace. If you are using attributes the tag must be closed with a
+'[tagname]>' where the [tagname] is the name of the tag used. See the
+'<p p>' example above.
+
+You can use any factor code at any point:
+  "text-align: " "red" 
+  <p style= 2dup cat2 p> 
+    "Using style " write swap write write
+  </p>
+    => <p style='text-align: red'>Using style text-align: red</p>
+
+Tags that are not normally closed are written using an XML style
+(ie. with a trailing slash):
+
+  "One" write <br/> "Two" write <br/> <input type= "text" input/>
+    => One<br>Two<br><input type='text'>
+
+Hello World 2
+=============
+
+Using the HTML generation library makes writing responders more
+readable. Here is the hello world example perviously using this
+system:
+
+  : hello-world2 ( -- )
+    [
+      drop
+      <html>
+        <head> <title> "Hello World" write </title> </head>
+        <body> "Hello World!" write </body>
+      </html>
+    ] show drop ;
+
+Install it using:
+
+  "helloworld2" [ hello-world2 ] install-cont-responder
+
+Dynamic Data
+============
+
+Adding dynamic data to the page is relatively easy. This example pulls
+a value from the 'room' word which displays the amount of available
+and free memory in the system.
+
+  : memory-stats1 ( -- )
+    [
+      drop
+      <html>
+        <head> <title> "Memory Statistics" write </title> </head>
+        <body>
+          <table border= "1" table>
+            <tr> 
+              <td> "Total Memory" write </td>
+              <td> room unparse write </td>
+            </tr>
+            <tr> 
+              <td> "Free Memory" write </td>
+              <td> unparse write </td>
+            </tr>
+         </table>
+       </body>
+      </html>
+    ] show drop ;
+
+  "memorystats1" [ memory-stats1 ] install-cont-responder
+
+Accessing this page will show a table with the current memory
+statistics. Hitting refresh will update the page with the latest
+information.
+
+The HTML output can be refactored into different words. For example:
+
+  : memory-stats-table ( free total -- )
+    #! Output a table containing the given statistcs.
+    <table border= "1" table>
+      <tr> 
+        <td> "Total Memory" write </td>
+        <td> unparse write </td>
+      </tr>
+      <tr> 
+        <td> "Free Memory" write </td>
+        <td> unparse write </td>
+      </tr>
+    </table> ;
+
+  : memory-stats2 ( -- )
+    [
+      drop
+      <html>
+        <head> <title> "Memory Statistics 2" write </title> </head>
+        <body> room memory-stats-table </body>
+      </html>
+    ] show drop ;
+
+  "memorystats2" [ memory-stats2 ] install-cont-responder
+
+Some simple flow
+================
+The big advantage with continuation based web servers is being able to
+write a web application in a standard procedural flow and have it
+correctly served up in the HTTP request/response model.
+
+This example demonstates a flow of three pages. Clicking an URL on the
+first page displays the second. Clicking an URL on the second displays
+the third.
+
+When a 'show' call is executed the page generated by the quotation is
+sent to the client. The computation of the responder is then
+'suspended'. When the client accesses a special URL computiation is
+resumed at the point of the end of the 'show' call. In this way
+procedural flow is maintained.
+
+This brings us to the 'URL' stack item that is available to the 'page
+generation' quotation passed to 'show'. This URL is a string that
+contains a URL that can be embedded in the page. When the user access
+that URL computation is resumed from the point of the end of the
+'show' call as described above:
+
+  : flow-example1 ( -- )
+    [
+      <html>
+        <head> <title> "Flow Example 1" write </title> </head>
+        <body>
+          <p> "Page 1" write </p>
+          <p> <a href= a> "Press to continue" write </a> </p>
+        </body>
+      </html>   
+    ] show drop 
+    [
+      <html>
+        <head> <title> "Flow Example 1" write </title> </head>
+        <body>
+          <p> "Page 2" write </p>
+          <p> <a href= a> "Press to continue" write </a> </p>
+        </body>
+      </html>   
+    ] show drop 
+    [
+      drop
+      <html>
+        <head> <title> "Flow Example 1" write </title> </head>
+        <body>
+          <p> "Page 3" write </p>
+        </body>
+      </html>   
+    ] show drop ;
+
+  "flowexample1" [ flow-example1 ] install-cont-responder
+
+The 'flow-example1' word contains three 'show' calls in a row. The
+first two display simple pages with an anchor link to the URL received
+on the stack. This URL when accessed resumes the computation. The
+final page just drops the URL. 
+
+When you display this example in the browser you'll be able to click
+the URL to navigate. You can use the back button to retry the URL's,
+you can clone the browser window and navigate them independantly, etc.
+
+The similarity of the functions above could so with some
+refactoring. The pages are almost exactly the same so we seperate this
+into a seperate word:
+
+  : show-flow-page ( n bool -- )
+  #! Show a page in the flow, using 'n' as the page number
+  #! to display. If 'bool' is true display a link to the 
+  #! next page.
+  [ ( n bool url -- )
+    <html>
+      <head> <title> "Flow Example 1" write </title> </head>
+      <body>
+        <p> "Page " write rot unparse write </p>
+        swap [
+          <p> <a href= a> "Press to continue" write </a> </p>
+        ] [
+          drop 
+        ] ifte
+      </body>
+    </html>   
+  ] show 3drop ;
+
+  : flow-example2 ( n -- )
+  #! Display the given number of pages in a row.
+  dup pred [ succ t show-flow-page ] times*
+  f show-flow-page ;
+  "flowexample2" [ 5 flow-example2 ] install-cont-responder
+
+In this example the 'show-flow-age' pulls the page number off the
+stack. It also gets whether or not to display the link to the next
+page. 
+
+Notice that after the show that a '3drop' is done whereas
+previously we've only done a single 'drop'. This is due to a side
+effect or 'show' using continuations. 
+
+After the 'show' call returns there will be one item on the stack
+(which we've been dropping and will explain later what it is). The
+stack will also be set as it was before the show call. So in this case
+the 'n' and 'bool' remain on the stack even though they were removed
+during the page generation quotation. This is because we resumed the
+continuation which, when captured, had those items on the stack. The
+general rule of thumb is you will need to account for items on the
+stack before the show call.
+
+This example also demonstrates using the 'times*' combinator to
+sequence the page shows. Any Factor code can be called and the
+continuation based system will sequentially display each page. The
+back button, browser window cloning, etc will all continue to work.
+
+You'll notice the URL's in the browser have a number at the end of
+them. This is the 'continuation identifier' which is like a session id
+except that it identifies not just the data you have stored but your
+location within the responder as well.
+
+Forms and POST data
+===================
+The web pages we've generated so far don't accept input from the
+user. I've mentioned previously that 'show' returns a value on the
+stack and we've been dropping it in our examples. 
+
+The value returned is a namespace containing the field names and
+values of any POST data in the request. If no POST data exists then it
+is the boolean value 'f'. 
+
+To process input from the user just put a form in the HTML with a
+method of 'POST' and an action set to the URL passed in to the page
+generation quotation. The show call will then return a namespace
+containing this data. Here is a simple example:
+
+  : accept-users-name ( -- name )
+    #! Display an HTML requesting the users name. Push
+    #! the name the user input on the stack..
+    [
+      <html>
+        <head> <title> "Please enter your name" write </title> </head>
+        <body>
+          <form action= method= "post" form>
+            <p> 
+              "Please enter your name:" write
+              <input type= "text" size= "20" name= "username" input/>
+              <input type= "submit" value= "Ok" input/>
+            </p>
+          </form>
+        </body>
+      </html>
+    ] show [
+      "username" get
+    ] bind ;
+
+  : post-example1 ( -- )
+    [
+      drop
+      <html>
+        <head> <title> "Hello!" write </title> </head>
+        <body>
+          <p> accept-users-name write ", Good to see you!" write </p>
+        </body>
+      </html>
+    ] show drop ;
+    
+  "post-example1" [ post-example1 ] install-cont-responder
+    
+The 'accept-users-name' word displays an HTML form allowing input of
+the name. When that form is submitted the namespace containing the
+data is returned by 'show'. We bind to it and retrieve the 'username'
+field. The name used here should be the same name used when creating
+the field in the HTML.
+
+'post-example1' then does something a bit tricky. Instead of first
+calling 'accept-users-name' to push the name on the stack and then
+displaying the resulting page we call 'accept-users-name' from within
+the page itself when we actually need it. The magic of the
+continuation system causes the 'accept-users-name' to be called when
+needed displaying that page first. It is certainly possible to do it
+the other way though:
+
+  : post-example2 ( -- )
+    accept-users-name
+    [ ( name url -- )
+      drop
+      <html>
+        <head> <title> "Hello!" write </title> </head>
+        <body>
+          <p> write ", Good to see you!" write </p>
+        </body>
+      </html>
+    ] show 2drop ;
+
+  "post-example2" [ post-example2 ] install-cont-responder
+
+Either way works. Notice that in the 'post-example2' we had to do a
+'2drop' instead of a 'drop' at the end of the show to remove the
+additional 'name' that is on the stack. This wasn't needed in
+'post-example1' because the 'name' was not on the stack at the time of
+the 'show' call.
\ No newline at end of file
index edb9c8b015fc0c431a1ee9ac51900708dceb460a..a1850fe01cedeb555a686e3bf75596c37c9bb5da 100644 (file)
@@ -45,7 +45,7 @@ USE: parser
   #! For a string this is everything but the first character.
   #! For a list this is the cdr.
   [
-    [ string? ] [ 1 str-tail ]
+    [ string? ] [ 1 swap str-tail ]
     [ list? ] [ cdr ]
   ] cond ;
 
@@ -77,7 +77,7 @@ USE: parser
   dup str-length pick < [
     2drop ""
   ] [
-    swap str-head
+    str-head
   ] ifte ;
 
 : (list-take) ( n list accum -- list )
@@ -107,7 +107,7 @@ USE: parser
   dup str-length pick < [
     2drop "" 
   ] [
-    swap str-tail 
+    str-tail 
   ] ifte ;
   
 : list-drop ( n list -- list )