Added parser combinator and lazy evaluation library.
parent
cfdaa293c9
commit
0c3fa9d74c
|
@ -0,0 +1,63 @@
|
||||||
|
! 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.
|
||||||
|
IN: lazy-examples
|
||||||
|
USE: lazy
|
||||||
|
USE: stack
|
||||||
|
USE: arithmetic
|
||||||
|
USE: lists
|
||||||
|
USE: combinators
|
||||||
|
USE: kernel
|
||||||
|
USE: logic
|
||||||
|
|
||||||
|
: lfrom ( n -- llist )
|
||||||
|
#! Return a lazy list of increasing numbers starting
|
||||||
|
#! from the initial value 'n'.
|
||||||
|
dup [ succ lfrom ] curry1 lcons ;
|
||||||
|
|
||||||
|
: lfrom-by ( n quot -- llist )
|
||||||
|
#! Return a lazy list of values starting from n, with
|
||||||
|
#! each successive value being the result of applying quot to
|
||||||
|
#! n.
|
||||||
|
dupd [ dup [ call ] dip lfrom-by ] curry2 lcons ;
|
||||||
|
|
||||||
|
: lnaturals 0 lfrom ;
|
||||||
|
: lpositves 1 lfrom ;
|
||||||
|
: levens 0 [ 2 + ] lfrom-by ;
|
||||||
|
: lodds 1 lfrom [ 2 mod 1 = ] lsubset ;
|
||||||
|
: lpowers-of-2 1 [ 2 * ] lfrom-by ;
|
||||||
|
: lones 1 [ ] lfrom-by ;
|
||||||
|
: lsquares lnaturals [ dup * ] lmap ;
|
||||||
|
: first-five-squares 5 lsquares ltake ;
|
||||||
|
|
||||||
|
: divisible-by? ( a b -- bool )
|
||||||
|
#! Return true if a is divisible by b
|
||||||
|
mod 0 = ;
|
||||||
|
|
||||||
|
: sieve ( llist - llist )
|
||||||
|
#! Given a lazy list of numbers, use the sieve of eratosthenes
|
||||||
|
#! algorithm to return a lazy list of primes.
|
||||||
|
luncons over [ divisible-by? not ] curry1 lsubset [ sieve ] curry1 lcons ;
|
||||||
|
|
||||||
|
: lprimes 2 lfrom sieve ;
|
||||||
|
|
||||||
|
: first-ten-primes 10 lprimes ltake ;
|
|
@ -0,0 +1,172 @@
|
||||||
|
! 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.
|
||||||
|
IN: lazy
|
||||||
|
USE: lists
|
||||||
|
USE: stack
|
||||||
|
USE: arithmetic
|
||||||
|
USE: stdio
|
||||||
|
USE: prettyprint
|
||||||
|
USE: kernel
|
||||||
|
USE: combinators
|
||||||
|
USE: logic
|
||||||
|
|
||||||
|
: curry1 ( n quot -- quot )
|
||||||
|
#! Return a quotation that when called will initially
|
||||||
|
#! have 'n' pushed on the stack.
|
||||||
|
cons ;
|
||||||
|
|
||||||
|
: curry2 ( n1 n2 quot -- quot )
|
||||||
|
#! Return a quotation that when called will initially
|
||||||
|
#! have 'n1' and 'n2' pushed on the stack.
|
||||||
|
cons cons ;
|
||||||
|
|
||||||
|
: delay ( value -- promise )
|
||||||
|
#! Return a promise that when 'forced' returns the original value.
|
||||||
|
unit ;
|
||||||
|
|
||||||
|
: force ( promise -- value )
|
||||||
|
#! Return the value associated with the promise.
|
||||||
|
call ;
|
||||||
|
|
||||||
|
: lcons ( car promise -- lcons )
|
||||||
|
#! Return a lazy pair, where the cdr is a promise and must
|
||||||
|
#! be forced to return the value.
|
||||||
|
cons ;
|
||||||
|
|
||||||
|
: lunit ( a -- llist )
|
||||||
|
#! Construct a lazy list of one element.
|
||||||
|
[ ] delay lcons ;
|
||||||
|
|
||||||
|
: lcar ( lcons -- car )
|
||||||
|
#! Return the car of a lazy pair.
|
||||||
|
car ;
|
||||||
|
|
||||||
|
: lcdr ( lcons -- cdr )
|
||||||
|
#! Return the cdr of a lazy pair, implicitly forcing it.
|
||||||
|
cdr force ;
|
||||||
|
|
||||||
|
: lnth ( n llist -- value )
|
||||||
|
#! Return the nth item in a lazy list
|
||||||
|
swap [ lcdr ] times lcar ;
|
||||||
|
|
||||||
|
: luncons ( lcons -- car cdr )
|
||||||
|
#! Return the car and forced cdr of the lazy cons.
|
||||||
|
uncons force ;
|
||||||
|
|
||||||
|
: (ltake) ( n llist accum -- list )
|
||||||
|
>r >r pred dup 0 < [
|
||||||
|
drop r> drop r> nreverse
|
||||||
|
] [
|
||||||
|
r> luncons swap r> cons (ltake)
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: ltake ( n llist -- list )
|
||||||
|
#! Return a list containing the first n items from
|
||||||
|
#! the lazy list.
|
||||||
|
[ ] (ltake) ;
|
||||||
|
|
||||||
|
: lmap ( llist quot -- llist )
|
||||||
|
#! Return a lazy list containing the collected result of calling
|
||||||
|
#! quot on the original lazy list.
|
||||||
|
over [ ] = [
|
||||||
|
2drop [ ]
|
||||||
|
] [
|
||||||
|
[ luncons ] dip
|
||||||
|
dup swapd
|
||||||
|
[ lmap ] curry2
|
||||||
|
[ call ] dip
|
||||||
|
lcons
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: lsubset ( llist pred -- llist )
|
||||||
|
#! Return a lazy list containing only the items from the original
|
||||||
|
#! lazy list for which the predicate returns a value other than f.
|
||||||
|
over [ ] = [
|
||||||
|
2drop [ ]
|
||||||
|
] [
|
||||||
|
[ luncons ] dip
|
||||||
|
dup swapd
|
||||||
|
[ lsubset ] curry2
|
||||||
|
-rot dupd call [
|
||||||
|
swap lcons
|
||||||
|
] [
|
||||||
|
drop call
|
||||||
|
] ifte
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: lappend* ;
|
||||||
|
: (lappend*) ;
|
||||||
|
: lappend-list* ;
|
||||||
|
|
||||||
|
: lappend-item* ( llists list item -- llist )
|
||||||
|
-rot [ lappend-list* ] curry2 lcons ;
|
||||||
|
|
||||||
|
: lappend-list* ( llists list -- llist )
|
||||||
|
dup [
|
||||||
|
#! non-empty list
|
||||||
|
luncons swap lappend-item*
|
||||||
|
] [
|
||||||
|
#! empty list
|
||||||
|
drop lappend*
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
|
||||||
|
: (lappend*) ( llists -- llist )
|
||||||
|
dup lcar [ ( llists )
|
||||||
|
#! Yes, the first item in the list is a valid llist
|
||||||
|
luncons swap lappend-list*
|
||||||
|
] [
|
||||||
|
#! The first item in the list is an empty list.
|
||||||
|
#! Resume passing the next list.
|
||||||
|
lcdr lappend*
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: lappend* ( llists -- llist )
|
||||||
|
#! Given a lazy list of lazy lists, return a lazy list that
|
||||||
|
#! works through all of the sub-lists in sequence.
|
||||||
|
dup [
|
||||||
|
(lappend*)
|
||||||
|
] [
|
||||||
|
#! Leave empty list on the stack
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: list>llist ( list -- llist )
|
||||||
|
#! Convert a list to a lazy list.
|
||||||
|
dup [
|
||||||
|
uncons [ list>llist ] curry1 lcons
|
||||||
|
] when ;
|
||||||
|
|
||||||
|
: lappend ( llist1 llist2 -- llist )
|
||||||
|
#! Concatenate two lazy lists such that they appear to be one big lazy list.
|
||||||
|
2list list>llist lappend* ;
|
||||||
|
|
||||||
|
: leach ( llist quot -- )
|
||||||
|
#! Call the quotation on each item in the lazy list.
|
||||||
|
#! Warning: If the list is infinite then this will
|
||||||
|
#! never return.
|
||||||
|
over [
|
||||||
|
>r luncons r> tuck >r >r call r> r> leach
|
||||||
|
] [
|
||||||
|
2drop
|
||||||
|
] ifte ;
|
||||||
|
|
|
@ -0,0 +1,280 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Lazy Evaluation</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Lazy Evaluation</h1>
|
||||||
|
<p>The 'lazy' vocabulary adds lazy lists to Factor. This provides the
|
||||||
|
ability to describe infinite structures, and to delay execution of
|
||||||
|
expressions until they are actually used.</p>
|
||||||
|
<p>Lazy lists, like normal lists, are composed of a head and tail. In
|
||||||
|
a lazy list the tail is something called a 'promise'. To convert a
|
||||||
|
'promise' into its actual value a word called 'force' is used. To
|
||||||
|
convert a value into a 'promise' the word to use is 'delay'.</p>
|
||||||
|
<p>Many of the lazy list words are named similar to the standard list
|
||||||
|
words but with an 'l' suffixed to it. Here are the commonly used
|
||||||
|
words and their equivalent list operation:</p>
|
||||||
|
<table border="1">
|
||||||
|
<tr><th>Lazy List</th><th>Normal List</th></tr>
|
||||||
|
<tr><td><a href="#lcons">lcons</a></td><td>cons</td></tr>
|
||||||
|
<tr><td><a href="#lunit">lunit</a></td><td>unit</td></tr>
|
||||||
|
<tr><td><a href="#lcar">lcar</a></td><td>car</td></tr>
|
||||||
|
<tr><td><a href="#lcdr">lcdr</a></td><td>cdr</td></tr>
|
||||||
|
<tr><td><a href="#lnth">lnth</a></td><td>nth</td></tr>
|
||||||
|
<tr><td><a href="#luncons">luncons</a></td><td>uncons</td></tr>
|
||||||
|
<tr><td><a href="#lmap">lmap</a></td><td>map</td></tr>
|
||||||
|
<tr><td><a href="#lsubset">lsubset</a></td><td>subset</td></tr>
|
||||||
|
<tr><td><a href="#leach">leach</a></td><td>each</td></tr>
|
||||||
|
<tr><td><a href="#lappend">lappend</a></td><td>append</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>A few additional words specific to lazy lists are:</p>
|
||||||
|
<table border="1">
|
||||||
|
<tr><td><a href="#ltake">ltake</a></td><td>Returns a normal list containing a specified
|
||||||
|
number of items from the lazy list.</td></tr>
|
||||||
|
<tr><td><a href="#lappendstar">lappend*</a></td><td>Given a lazy list of lazy lists,
|
||||||
|
concatenate them together in a lazy manner, returning a single lazy
|
||||||
|
list.</td></tr>
|
||||||
|
<tr><td><a href="#list>llist">list>llist</a></td><td>Given a normal list, return a lazy list
|
||||||
|
that contains the same elements as the normal list.</td></tr>
|
||||||
|
</table>
|
||||||
|
<p>A couple of helper functions are also provided by the lazy
|
||||||
|
vocabulary.</p>
|
||||||
|
<table border="1">
|
||||||
|
<tr><td>curry1</td><td>Given a value and a quotation, returns a new
|
||||||
|
quotation that when called will have the value on the stack.</td></tr>
|
||||||
|
<tr><td>curry2</td><td>Given two values and a quotation, returns a new
|
||||||
|
quotation that when called will have the two values on the
|
||||||
|
stack.</td></tr>
|
||||||
|
</table>
|
||||||
|
<h2>Reference</h2>
|
||||||
|
<!-- lcons description -->
|
||||||
|
<a name="lcons">
|
||||||
|
<h3>lcons ( value promise -- lcons )</h3>
|
||||||
|
<p>Provides the same effect as 'cons' does for normal lists. It
|
||||||
|
creates a cons cell where the first element is the value given and the
|
||||||
|
second element is a promise.</p>
|
||||||
|
<a name="promise">
|
||||||
|
<p>A promise is either a value that has had 'force' called on it, or
|
||||||
|
a quotation that when 'call' is applied to it, returns the actual
|
||||||
|
value.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) 5 6 delay <a href="#lcons">lcons</a> dup .
|
||||||
|
=> [ 5 6 ]
|
||||||
|
( 2 ) dup <a href="#lcar">lcar</a> .
|
||||||
|
=> 5
|
||||||
|
( 3 ) dup <a href="#lcdr">lcdr</a> .
|
||||||
|
=> 6
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- lunit description -->
|
||||||
|
<a name="lunit">
|
||||||
|
<h3>lunit ( value -- llist )</h3>
|
||||||
|
<p>Provides the same effect as 'unit' does for normal lists. It
|
||||||
|
creates a lazy list where the first element is the value given.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) 42 <a href="#lunit">lunit</a> dup .
|
||||||
|
=> [ 42 f ]
|
||||||
|
( 2 ) dup <a href="#lcar">lcar</a> .
|
||||||
|
=> 42
|
||||||
|
( 3 ) dup <a href="#lcdr">lcdr</a> .
|
||||||
|
=> f
|
||||||
|
( 4 ) [ . ] <a href="#leach">leach</a>
|
||||||
|
=> 42
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- lcar description -->
|
||||||
|
<a name="lcar">
|
||||||
|
<h3>lcar ( lcons -- value )</h3>
|
||||||
|
<p>Provides the same effect as 'car' does for normal lists. It
|
||||||
|
returns the first element in a lazy cons cell.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) 42 <a href="#lunit">lunit</a> dup .
|
||||||
|
=> [ 42 f ]
|
||||||
|
( 2 ) <a href="#lcar">lcar</a> .
|
||||||
|
=> 42
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- lcdr description -->
|
||||||
|
<a name="lcdr">
|
||||||
|
<h3>lcdr ( lcons -- value )</h3>
|
||||||
|
<p>Provides the same effect as 'cdr' does for normal lists. It
|
||||||
|
returns the second element in a lazy cons cell and forces it. This
|
||||||
|
causes that element to be evaluated immediately.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) 5 [ 5 6 + ] <a href="#lcons">lcons</a> dup .
|
||||||
|
=> [ 5 5 6 + ]
|
||||||
|
( 2 ) <a href="#lcdr">lcdr</a> .
|
||||||
|
=> 11
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) 5 lfrom dup .
|
||||||
|
=> [ 5 5 succ lfrom ]
|
||||||
|
( 2 ) <a href="#lcdr">lcdr</a> dup <a href="#lcar">lcar</a> .
|
||||||
|
=> 6
|
||||||
|
( 3 ) <a href="#lcdr">lcdr</a> dup <a href="#lcar">lcar</a> .
|
||||||
|
=> 7
|
||||||
|
( 4 ) <a href="#lcdr">lcdr</a> dup <a href="#lcar">lcar</a> .
|
||||||
|
=> 8
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- lnth description -->
|
||||||
|
<a name="lnth">
|
||||||
|
<h3>lnth ( n llist -- value )</h3>
|
||||||
|
<p>Provides the same effect as 'nth' does for normal lists. It
|
||||||
|
returns the nth value in the lazy list. It causes all the values up to
|
||||||
|
'n' to be evaluated.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) 1 lfrom
|
||||||
|
=> [ 1 1 succ lfrom ]
|
||||||
|
( 2 ) 5 swap <a href="#lnth">lnth</a> .
|
||||||
|
=> 6
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- luncons description -->
|
||||||
|
<a name="luncons">
|
||||||
|
<h3>luncons ( lcons -- car cdr )</h3>
|
||||||
|
<p>Provides the same effect as 'uncons' does for normal lists. It
|
||||||
|
returns the car and cdr of the lazy list. Note that cdr is forced
|
||||||
|
resulting in it being evaluated.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) 5 [ 6 ] <a href="#lcons">lcons</a> dup .
|
||||||
|
=> [ 5 6 ]
|
||||||
|
( 2 ) <a href="#luncons">luncons</a> .s
|
||||||
|
=> { 5 6 }
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- lmap description -->
|
||||||
|
<a name="lmap">
|
||||||
|
<h3>lmap ( llist quot -- llist )</h3>
|
||||||
|
<p>Provides the same effect as 'map' does for normal lists. It
|
||||||
|
lazily maps over a lazy list applying the quotation to each element.
|
||||||
|
A new lazy list is returned which contains the results of the
|
||||||
|
quotation.</p>
|
||||||
|
<p>When initially called <a href="#lmap">lmap</a> will only call quot on the first element
|
||||||
|
of the list. It then constructs a lazy list that performs the
|
||||||
|
next '<a href="#lmap">lmap</a>' operation on the next element when it is evaluated. This
|
||||||
|
allows mapping over infinite lists.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) 1 lfrom
|
||||||
|
=> < infinite list of incrementing numbers >
|
||||||
|
( 2 ) [ 2 * ] <a href="#lmap">lmap</a>
|
||||||
|
=> < infinite list of numbers incrementing by 2 >
|
||||||
|
( 3 ) 5 swap <a href="#ltake">ltake</a> .
|
||||||
|
=> [ 2 4 6 8 10 ]
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- lsubset description -->
|
||||||
|
<a name="lsubset">
|
||||||
|
<h3>lsubset ( llist pred -- llist )</h3>
|
||||||
|
<p>Provides the same effect as 'subset' does for normal lists. It
|
||||||
|
lazily iterates over a lazy list applying the predicate quotation to each
|
||||||
|
element. If that quotation returns true, the element will be included
|
||||||
|
in the resulting lazy list. If it is false, the element will be skipped.
|
||||||
|
A new lazy list is returned which contains all elements where the
|
||||||
|
predicate returned true.</p>
|
||||||
|
<p>When initially called <a href="#lsubset">lsubset</a> will only call
|
||||||
|
the predicate quotation on the first element
|
||||||
|
of the list. It then constructs a lazy list that performs the
|
||||||
|
next '<a href="#lsubset">lsubset</a>' operation on the next element when it is evaluated. This
|
||||||
|
allows subsetting over infinite lists.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) 1 lfrom
|
||||||
|
=> < infinite list of incrementing numbers >
|
||||||
|
( 2 ) [ prime? ] <a href="#lsubset">lsubset</a>
|
||||||
|
=> < infinite list of prime numbers >
|
||||||
|
( 3 ) 5 swap <a href="#ltake">ltake</a> .
|
||||||
|
=> [ 2 3 5 7 11 ]
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- leach description -->
|
||||||
|
<a name="leach">
|
||||||
|
<h3>leach ( llist quot -- )</h3>
|
||||||
|
<p>Provides the same effect as 'each' does for normal lists. It
|
||||||
|
lazily iterates over a lazy list applying the quotation to each
|
||||||
|
element. If this operation is applied to an infinite list it will
|
||||||
|
never return unless the quotation escapes out by calling a continuation.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) 1 lfrom
|
||||||
|
=> < infinite list of incrementing numbers >
|
||||||
|
( 2 ) [ 2 mod 1 = ] <a href="#lsubset">lsubset</a>
|
||||||
|
=> < infinite list of odd numbers >
|
||||||
|
( 3 ) [ . ] <a href="#leach">leach</a>
|
||||||
|
=> 1
|
||||||
|
3
|
||||||
|
5
|
||||||
|
7
|
||||||
|
... for ever ...
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- ltake description -->
|
||||||
|
<a name="ltake">
|
||||||
|
<h3>ltake ( n llist -- list )</h3>
|
||||||
|
<p>Iterates over the lazy list 'n' times, appending each element to a
|
||||||
|
normal list. The normal list is returned. This provides a convenient
|
||||||
|
way of getting elements out of a lazy list.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) : ones 1 [ ones ] <a href="#lcons">lcons</a> ;
|
||||||
|
( 2 ) 5 ones <a href="#ltake">ltake</a>
|
||||||
|
=> [ 1 1 1 1 1 ]
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- lappend description -->
|
||||||
|
<a name="lappend">
|
||||||
|
<h3>lappend ( llist1 llist2 -- llist )</h3>
|
||||||
|
<p>Lazily appends two lists together. The actual appending is done
|
||||||
|
lazily on iteration rather than immediately so it works very fast no
|
||||||
|
matter how large the list.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) [ 1 2 3 ] <a href="#list>llist">list>llist</a> [ 4 5 6 ] <a href="#list>llist">list>llist</a> <a href="#lappend">lappend</a>
|
||||||
|
( 2 ) [ . ] <a href="#leach">leach</a>
|
||||||
|
=> 1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- lappend* description -->
|
||||||
|
<a name="lappendstar">
|
||||||
|
<h3>lappend* ( llists -- llist )</h3>
|
||||||
|
<p>Given a lazy list of lazy lists, concatenate them together in a
|
||||||
|
lazy fashion. The actual appending is done lazily on iteration rather
|
||||||
|
than immediately so it works very fast no matter how large the lists.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) [ 1 2 3 ] <a href="#list>llist">list>llist</a>
|
||||||
|
( 2 ) [ 4 5 6 ] <a href="#list>llist">list>llist</a>
|
||||||
|
( 3 ) [ 7 8 9 ] <a href="#list>llist">list>llist</a>
|
||||||
|
( 4 ) 3list <a href="#list>llist">list>llist</a> <a href="#lappendstar">lappend*</a>
|
||||||
|
( 5 ) [ . ] <a href="#leach">leach</a>
|
||||||
|
=> 1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- list>llist description -->
|
||||||
|
<a name="list>llist">
|
||||||
|
<h3>list>llist ( list -- llist )</h3>
|
||||||
|
<p>Converts a normal list into a lazy list. This is done lazily so the
|
||||||
|
initial list is not iterated through immediately.</p>
|
||||||
|
<pre class="code">
|
||||||
|
( 1 ) [ 1 2 3 ] <a href="#list>llist">list>llist</a>
|
||||||
|
( 2 ) [ . ] <a href="#leach">leach</a>
|
||||||
|
=> 1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p class="footer">
|
||||||
|
News and updates to this software can be obtained from the authors
|
||||||
|
weblog: <a href="http://radio.weblogs.com/0102385">Chris Double</a>.</p>
|
||||||
|
<p id="copyright">Copyright (c) 2004, Chris Double. All Rights Reserved.</p>
|
||||||
|
</body> </html>
|
|
@ -0,0 +1,433 @@
|
||||||
|
! 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.
|
||||||
|
IN: parser-combinators
|
||||||
|
USE: lazy
|
||||||
|
USE: stack
|
||||||
|
USE: lists
|
||||||
|
USE: strings
|
||||||
|
USE: arithmetic
|
||||||
|
USE: logic
|
||||||
|
USE: kernel
|
||||||
|
USE: combinators
|
||||||
|
USE: parser
|
||||||
|
|
||||||
|
: phead ( object -- head )
|
||||||
|
#! Polymorphic head. Return the head item of the object.
|
||||||
|
#! For a string this is the first character.
|
||||||
|
#! For a list this is the car.
|
||||||
|
[
|
||||||
|
[ string? ] [ 0 swap str-nth ]
|
||||||
|
[ list? ] [ car ]
|
||||||
|
] cond ;
|
||||||
|
|
||||||
|
: ptail ( object -- tail )
|
||||||
|
#! Polymorphic tail. Return the tail of the object.
|
||||||
|
#! For a string this is everything but the first character.
|
||||||
|
#! For a list this is the cdr.
|
||||||
|
[
|
||||||
|
[ string? ] [ 1 str-tail ]
|
||||||
|
[ list? ] [ cdr ]
|
||||||
|
] cond ;
|
||||||
|
|
||||||
|
: pfirst ( object -- first )
|
||||||
|
#! Polymorphic first
|
||||||
|
phead ;
|
||||||
|
|
||||||
|
: psecond ( object -- second )
|
||||||
|
#! Polymorphic second
|
||||||
|
[
|
||||||
|
[ string? ] [ 1 swap str-nth ]
|
||||||
|
[ list? ] [ cdr car ]
|
||||||
|
] cond ;
|
||||||
|
|
||||||
|
: ph:t ( object -- head tail )
|
||||||
|
#! Return the head and tail of the object.
|
||||||
|
dup phead swap ptail ;
|
||||||
|
|
||||||
|
: pempty? ( object -- bool )
|
||||||
|
#! Polymorphic empty test.
|
||||||
|
[
|
||||||
|
[ string? ] [ "" = ]
|
||||||
|
[ list? ] [ not ]
|
||||||
|
] cond ;
|
||||||
|
|
||||||
|
: string-take ( n string -- string )
|
||||||
|
#! Return a string with the first 'n' characters
|
||||||
|
#! of the original string.
|
||||||
|
dup str-length pick < [
|
||||||
|
2drop ""
|
||||||
|
] [
|
||||||
|
swap str-head
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: (list-take) ( n list accum -- list )
|
||||||
|
>r >r pred dup 0 < [
|
||||||
|
drop r> drop r> nreverse
|
||||||
|
] [
|
||||||
|
r> uncons swap r> cons (list-take)
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: list-take ( n list -- list )
|
||||||
|
#! Return a list with the first 'n' characters
|
||||||
|
#! of the original list.
|
||||||
|
[ ] (list-take) ;
|
||||||
|
|
||||||
|
: ptake ( n object -- object )
|
||||||
|
#! Polymorphic take.
|
||||||
|
#! Return a collection of the first 'n'
|
||||||
|
#! characters from the original collection.
|
||||||
|
[
|
||||||
|
[ string? ] [ string-take ]
|
||||||
|
[ list? ] [ list-take ]
|
||||||
|
] cond ;
|
||||||
|
|
||||||
|
: string-drop ( n string -- string )
|
||||||
|
#! Return a string with the first 'n' characters
|
||||||
|
#! of the original string removed.
|
||||||
|
dup str-length pick < [
|
||||||
|
2drop ""
|
||||||
|
] [
|
||||||
|
swap str-tail
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: list-drop ( n list -- list )
|
||||||
|
#! Return a list with the first 'n' items
|
||||||
|
#! of the original list removed.
|
||||||
|
>r pred dup 0 < [
|
||||||
|
drop r>
|
||||||
|
] [
|
||||||
|
r> cdr list-drop
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: pdrop ( n object -- object )
|
||||||
|
#! Polymorphic drop.
|
||||||
|
#! Return a collection the same as 'object'
|
||||||
|
#! but with the first n items removed.
|
||||||
|
[
|
||||||
|
[ string? ] [ string-drop ]
|
||||||
|
[ list? ] [ list-drop ]
|
||||||
|
] cond ;
|
||||||
|
|
||||||
|
: ifte-head= ( string-or-list ch [ quot1 ] [ quot2 ] -- )
|
||||||
|
#! When the character 'ch' is equal to the head
|
||||||
|
#! of the string or list, run the quot1 otherwise run quot2.
|
||||||
|
[ swap phead = ] 2dip ifte ;
|
||||||
|
|
||||||
|
: symbol ( ch -- parser )
|
||||||
|
#! Return a parser that parses the given symbol.
|
||||||
|
[ ( inp ch -- result )
|
||||||
|
2dup [
|
||||||
|
swap ptail cons lunit
|
||||||
|
] [
|
||||||
|
2drop [ ]
|
||||||
|
] ifte-head=
|
||||||
|
] curry1 ;
|
||||||
|
|
||||||
|
: token ( string -- parser )
|
||||||
|
#! Return a parser that parses the given string.
|
||||||
|
[ ( inp string -- result )
|
||||||
|
2dup str-length swap ptake over = [
|
||||||
|
swap over str-length swap pdrop cons lunit
|
||||||
|
] [
|
||||||
|
2drop [ ]
|
||||||
|
] ifte
|
||||||
|
] curry1 ;
|
||||||
|
|
||||||
|
: satisfy ( p -- parser )
|
||||||
|
#! Return a parser that succeeds if the predicate 'p',
|
||||||
|
#! when passed the first character in the input, returns
|
||||||
|
#! true.
|
||||||
|
[ ( inp p -- result )
|
||||||
|
over pempty? [
|
||||||
|
2drop [ ]
|
||||||
|
] [
|
||||||
|
over phead swap call [
|
||||||
|
ph:t cons lunit
|
||||||
|
] [
|
||||||
|
drop [ ]
|
||||||
|
] ifte
|
||||||
|
] ifte
|
||||||
|
] curry1 ;
|
||||||
|
|
||||||
|
: satisfy2 ( p r -- parser )
|
||||||
|
#! Return a parser that succeeds if the predicate 'p',
|
||||||
|
#! when passed the first character in the input, returns
|
||||||
|
#! true. On success the word 'r' is called with the
|
||||||
|
#! successfully parser character on the stack. The result
|
||||||
|
#! of this is returned as the result of the parser.
|
||||||
|
[ ( inp p r -- result )
|
||||||
|
>r over phead swap call [
|
||||||
|
ph:t swap r> call swons lunit
|
||||||
|
] [
|
||||||
|
r> 2drop [ ]
|
||||||
|
] ifte
|
||||||
|
] curry2 ;
|
||||||
|
|
||||||
|
: epsilon ( -- parser )
|
||||||
|
#! A parser that parses the empty string.
|
||||||
|
[ ( inp -- result )
|
||||||
|
"" swap cons lunit
|
||||||
|
] ;
|
||||||
|
|
||||||
|
: succeed ( r -- parser )
|
||||||
|
#! A parser that always returns 'r' and consumes no input.
|
||||||
|
[ ( inp r -- result )
|
||||||
|
swap cons lunit
|
||||||
|
] curry1 ;
|
||||||
|
|
||||||
|
: fail ( -- parser )
|
||||||
|
#! A parser that always fails
|
||||||
|
[
|
||||||
|
drop [ ]
|
||||||
|
] ;
|
||||||
|
|
||||||
|
USE: prettyprint
|
||||||
|
USE: unparser
|
||||||
|
|
||||||
|
: ensure-list ( a -- [ a ] )
|
||||||
|
#! If 'a' is not a list, make it one.
|
||||||
|
dup list? [ unit ] unless ;
|
||||||
|
|
||||||
|
: ++ ( a b -- [ a b ] )
|
||||||
|
#! Join two items into a list.
|
||||||
|
>r ensure-list r> ensure-list append ;
|
||||||
|
|
||||||
|
: <&> ( p1 p2 -- parser )
|
||||||
|
#! Sequentially combine two parsers, returning a parser
|
||||||
|
#! that first calls p1, then p2 all remaining results from
|
||||||
|
#! p1.
|
||||||
|
[ ( inp p1 p2 -- result )
|
||||||
|
>r call r> [ ( [ x | xs ] p2 -- result )
|
||||||
|
>r uncons r> call swap [ ( [ x2 | xs2 ] x -- result )
|
||||||
|
>r uncons swap r> swap ++ swons
|
||||||
|
] curry1 lmap
|
||||||
|
] curry1 lmap lappend*
|
||||||
|
] curry2 ;
|
||||||
|
|
||||||
|
|
||||||
|
: <|> ( p1 p2 -- parser )
|
||||||
|
#! Choice operator for parsers. Return a parser that does
|
||||||
|
#! p1 or p2 depending on which will succeed.
|
||||||
|
[ ( inp p1 p2 -- result )
|
||||||
|
rot tuck swap call >r swap call r> lappend
|
||||||
|
] curry2 ;
|
||||||
|
|
||||||
|
: p-abc ( -- parser )
|
||||||
|
#! Test Parser. Parses the string "abc"
|
||||||
|
"a" token "b" token "c" token <&> <&> ;
|
||||||
|
|
||||||
|
: parse-skipwhite ( string -- string )
|
||||||
|
dup phead blank? [
|
||||||
|
ptail parse-skipwhite
|
||||||
|
] [
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: sp ( parser -- parser )
|
||||||
|
#! Return a parser that first skips all whitespace before
|
||||||
|
#! parsing.
|
||||||
|
[ ( inp parser -- result )
|
||||||
|
[ parse-skipwhite ] dip call
|
||||||
|
] curry1 ;
|
||||||
|
|
||||||
|
: just ( parser -- parser )
|
||||||
|
#! Return a parser that works exactly like the input parser
|
||||||
|
#! but guarantees that the rest string is empty.
|
||||||
|
[ ( inp parser -- result )
|
||||||
|
call [ ( [ x | xs ] -- )
|
||||||
|
cdr str-length 0 =
|
||||||
|
] lsubset
|
||||||
|
] curry1 ;
|
||||||
|
|
||||||
|
: <@ ( p f -- parser )
|
||||||
|
#! Given a parser p and a quotation f return a parser
|
||||||
|
#! that does the same as p but in addition applies f
|
||||||
|
#! to the resulting parse tree.
|
||||||
|
[ ( inp p f -- result )
|
||||||
|
>r call r> [ ( [ x | xs ] f -- [ fx | xs ] )
|
||||||
|
swap uncons [ swap over [ call ] [ drop ] ifte ] dip cons
|
||||||
|
] curry1 lmap
|
||||||
|
] curry2 ;
|
||||||
|
|
||||||
|
: p-1 ( -- parser )
|
||||||
|
"1" token "123" swap call lcar ;
|
||||||
|
|
||||||
|
: p-2 ( -- parser )
|
||||||
|
"1" token [ str>number ] <@ "123" swap call lcar ;
|
||||||
|
|
||||||
|
: some ( parser -- det-parser )
|
||||||
|
#! Given a parser, return a parser that only produces the
|
||||||
|
#! resulting parse tree of the first successful complete parse.
|
||||||
|
[ ( inp parser -- result )
|
||||||
|
just call lcar car
|
||||||
|
] curry1 ;
|
||||||
|
|
||||||
|
: delayed-parser ( [ parser ] -- parser )
|
||||||
|
[ ( inp [ parser ] -- result )
|
||||||
|
call call
|
||||||
|
] curry1 ;
|
||||||
|
|
||||||
|
: parens ;
|
||||||
|
: parens ( -- parser )
|
||||||
|
#! Parse nested parentheses
|
||||||
|
"(" token [ parens ] delayed-parser <&>
|
||||||
|
")" token <&> [ parens ] delayed-parser <&>
|
||||||
|
epsilon <|> ;
|
||||||
|
|
||||||
|
: nesting ( -- parser )
|
||||||
|
#! Count the maximum depth of nested parentheses.
|
||||||
|
"(" token [ nesting ] delayed-parser <&> ")" token <&>
|
||||||
|
[ nesting ] delayed-parser <&> [ .s drop "a" ] <@ epsilon <|> ;
|
||||||
|
|
||||||
|
: <& ( parser1 parser2 -- parser )
|
||||||
|
#! Same as <&> except only return the first item in the parse tree.
|
||||||
|
<&> [ pfirst ] <@ ;
|
||||||
|
|
||||||
|
: &> ( parser1 parser2 -- parser )
|
||||||
|
#! Same as <&> except only return the second item in the parse tree.
|
||||||
|
<&> [ psecond ] <@ ;
|
||||||
|
|
||||||
|
: lst ( [ x [ xs ] ] -- [x:xs] )
|
||||||
|
#! I need a good name for this word...
|
||||||
|
dup cdr [ uncons car cons ] when unit ;
|
||||||
|
|
||||||
|
: <*> ( parser -- parser )
|
||||||
|
#! Return a parser that accepts zero or more occurences of the original
|
||||||
|
#! parser.
|
||||||
|
dup [ <*> ] curry1 delayed-parser <&> [ lst ] <@ [ ] succeed <|> ;
|
||||||
|
|
||||||
|
: <+> ( parser -- parser )
|
||||||
|
#! Return a parser that accepts one or more occurences of the original
|
||||||
|
#! parser.
|
||||||
|
dup [ <*> ] curry1 delayed-parser <&> [ lst ] <@ ;
|
||||||
|
|
||||||
|
: <?> ( parser -- parser )
|
||||||
|
#! Return a parser where its construct is optional. It may or may not occur.
|
||||||
|
[ ] succeed <|> ;
|
||||||
|
|
||||||
|
: <first> ( parser -- parser )
|
||||||
|
#! Transform a parser into a parser that only returns the first success.
|
||||||
|
[
|
||||||
|
call dup [ lcar lunit ] when
|
||||||
|
] curry1 ;
|
||||||
|
|
||||||
|
: <!*> ( parser -- parser )
|
||||||
|
#! Version of <*> that only returns the first success.
|
||||||
|
<*> <first> ;
|
||||||
|
|
||||||
|
: <!+> ( parser -- parser )
|
||||||
|
#! Version of <+> that only returns the first success.
|
||||||
|
<+> <first> ;
|
||||||
|
|
||||||
|
: ab-test
|
||||||
|
"a" token <*> "b" token <&> "aaaaab" swap call [ . ] leach ;
|
||||||
|
|
||||||
|
: ab-test2
|
||||||
|
"a" token <*> "b" token <&> [ "a" "a" "a" "b" ] swap call [ . ] leach ;
|
||||||
|
|
||||||
|
: a "a" token "a" token <&> epsilon <|> ;
|
||||||
|
: b "b" token epsilon <|> ;
|
||||||
|
: c "c" token "c" token <&> ;
|
||||||
|
: d "d" token "d" token <&> ;
|
||||||
|
: count-a "a" token [ count-a ] delayed-parser &> "b" token <& [ 1 + ] <@ 0 succeed <|> ;
|
||||||
|
: tca "aaabbb" count-a call [ . ] leach ;
|
||||||
|
|
||||||
|
: parse-digit ( -- parser )
|
||||||
|
#! Return a parser for digits
|
||||||
|
[ digit? ] satisfy [ CHAR: 0 - ] <@ ;
|
||||||
|
|
||||||
|
: (reduce) ( start quot list -- value )
|
||||||
|
#! Call quot with start and the first value in the list.
|
||||||
|
#! quot is then called with the result of quot and the
|
||||||
|
#! next item in the list until the list is exhausted.
|
||||||
|
uncons >r swap dup dip r> dup [
|
||||||
|
(reduce)
|
||||||
|
] [
|
||||||
|
2drop
|
||||||
|
] ifte ;
|
||||||
|
|
||||||
|
: reduce ( list start quot -- value )
|
||||||
|
#! Call quot with start and the first value in the list.
|
||||||
|
#! quot is then called with the result of quot and the
|
||||||
|
#! next item in the list until the list is exhausted.
|
||||||
|
rot (reduce) ;
|
||||||
|
|
||||||
|
: natural ( -- parser )
|
||||||
|
#! a parser for natural numbers.
|
||||||
|
parse-digit <*> [ car 0 [ swap 10 * + ] reduce unit ] <@ ;
|
||||||
|
|
||||||
|
: natural2 ( -- parser )
|
||||||
|
#! a parser for natural numbers.
|
||||||
|
parse-digit <!+> [ car 0 [ swap 10 * + ] reduce unit ] <@ ;
|
||||||
|
|
||||||
|
: integer ( -- parser )
|
||||||
|
#! A parser that can parser possible negative numbers.
|
||||||
|
"-" token <?> [ drop -1 ] <@ natural2 <&> [ 1 [ * ] reduce ] <@ ;
|
||||||
|
|
||||||
|
: identifier ( -- parser )
|
||||||
|
#! Parse identifiers
|
||||||
|
[ letter? ] satisfy <+> [ car cat ] <@ ;
|
||||||
|
|
||||||
|
: identifier2 ( -- parser )
|
||||||
|
#! Parse identifiers
|
||||||
|
[ letter? ] satisfy <!+> [ car cat ] <@ ;
|
||||||
|
|
||||||
|
: ints ( -- parser )
|
||||||
|
integer "+" token [ drop [ [ + ] ] ] <@ <&>
|
||||||
|
integer <&> [ call swap call ] <@ ;
|
||||||
|
|
||||||
|
: url-quotable ( -- parser )
|
||||||
|
! [a-zA-Z0-9/_?] re-matches
|
||||||
|
[ letter? ] satisfy
|
||||||
|
[ LETTER? ] satisfy <|>
|
||||||
|
[ digit? ] satisfy <|>
|
||||||
|
CHAR: / symbol <|>
|
||||||
|
CHAR: _ symbol <|>
|
||||||
|
CHAR: ? symbol <|> just ;
|
||||||
|
|
||||||
|
: http-header ( -- parser )
|
||||||
|
[ CHAR: : = not ] satisfy <!+> [ car cat ] <@
|
||||||
|
": " token [ drop f ] <@ <&>
|
||||||
|
[ drop t ] satisfy <!+> [ car cat ] <@ <&> just ;
|
||||||
|
|
||||||
|
: parse-http-header ( string -- [ name value ] )
|
||||||
|
http-header call lcar car ;
|
||||||
|
|
||||||
|
: get-request ( -- parser )
|
||||||
|
"GET" token
|
||||||
|
[ drop t ] satisfy <!+> sp [ car cat ] <@ <&> ;
|
||||||
|
|
||||||
|
: post-request ( -- parser )
|
||||||
|
"POST" token
|
||||||
|
[ drop t ] satisfy <!+> sp [ car cat ] <@ <&> ;
|
||||||
|
|
||||||
|
: all-request ( -- parser )
|
||||||
|
"POST" token
|
||||||
|
[ 32 = not ] satisfy <!+> sp [ car cat ] <@ <&>
|
||||||
|
"HTTP/1.0" token sp <&> ;
|
||||||
|
|
||||||
|
: split-url ( -- parser )
|
||||||
|
"http://" token
|
||||||
|
[ CHAR: / = not ] satisfy <!*> [ car cat ] <@ <&>
|
||||||
|
"/" token <&>
|
||||||
|
[ drop t ] satisfy <!*> [ car cat ] <@ <&> ;
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Parser Combinators</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Parsers</h1>
|
||||||
|
<p>A parser is a word or quotation that, when called, processes
|
||||||
|
an input string on the stack, performs some parsing operation on
|
||||||
|
it, and returns a result indicating the success of the parsing
|
||||||
|
operation.</p>
|
||||||
|
<p>The result returned by a parser is known as a 'list of
|
||||||
|
successes'. It is a lazy list of standard Factor cons cells. Each cons
|
||||||
|
cell is a result of a parse. The car of the cell is the result of the
|
||||||
|
parse operation and the cdr of the cell is the remaining input left to
|
||||||
|
be parsed.</p>
|
||||||
|
<p>A list is used for the result as a parse operation can potentially
|
||||||
|
return many successful results. For example, a parser that parses one
|
||||||
|
or more digits will return more than one result for the input "123". A
|
||||||
|
successful parse could be "1", "12" or "123".</p>
|
||||||
|
<p>The list is lazy so if only one parse result is required the
|
||||||
|
remaining results won't actually be processed if they are not
|
||||||
|
requested. This improves efficiency.</p>
|
||||||
|
<p>The car of the result pair can be any value that the parser wishes
|
||||||
|
to return. It could be the successful portion of the input string
|
||||||
|
parsed, an abstract syntax tree representing the parsed input, or even
|
||||||
|
a quotation that should get called for later processing.</p>
|
||||||
|
<p>A Parser Combinator is a word that takes one or more parsers and
|
||||||
|
returns a parser that when called uses the original parsers in some
|
||||||
|
manner.</p>
|
||||||
|
<h1>Example Parsers</h1>
|
||||||
|
<p>The following are some very simple parsers that demonstrate how
|
||||||
|
general parsers work and the 'list of sucesses' that are returned as a
|
||||||
|
result.</p>
|
||||||
|
<pre class="code">
|
||||||
|
(1) : char-a ( inp -- result )
|
||||||
|
0 over str-nth CHAR: a = [
|
||||||
|
1 str-tail CHAR: a swons lunit
|
||||||
|
] [
|
||||||
|
drop f
|
||||||
|
] ifte ;
|
||||||
|
(2) "atest" char-a [ [ . ] leach ] when*
|
||||||
|
=> [ 97 | "test" ]
|
||||||
|
(3) "test" char-a [ [ . ] leach ] when*
|
||||||
|
=>
|
||||||
|
</pre>
|
||||||
|
<p>'char-a' is a parser that only accepts the character 'a' in the
|
||||||
|
input string. When passed an input string with a string with a leading
|
||||||
|
'a' then the 'list of successes' has 1 result value. The car of that
|
||||||
|
result value is the character 'a' successfully parsed, and the cdr is
|
||||||
|
the remaining input string. On failure of the parse an empty list is
|
||||||
|
returned.</p>
|
||||||
|
<p>The parser combinator library provides a combinator, <&>, that takes
|
||||||
|
two parsers off the stack and returns a parser that calls the original
|
||||||
|
two in sequence. An example of use would be calling 'char-a' twice,
|
||||||
|
which would then result in an input string expected with two 'a'
|
||||||
|
characters leading:</p>
|
||||||
|
<pre class="code">
|
||||||
|
(1) "aatest" [ char-a ] [ char-a ] <&> call
|
||||||
|
=> < list of successes >
|
||||||
|
(2) [ . ] leach
|
||||||
|
=> [ [ 97 97 ] | "test" ]
|
||||||
|
</pre>
|
||||||
|
<p>Creating parsers for specfic characters and tokens can be a chore
|
||||||
|
so there is a word that, given a string token on the stack, returns
|
||||||
|
a parser that parses that particular token:</p>
|
||||||
|
<pre class="code">
|
||||||
|
(1) "begin" token
|
||||||
|
=> < a parser that parses the token "begin" >
|
||||||
|
(2) dup "this should fail" swap call .
|
||||||
|
=> f
|
||||||
|
(3) "begin a successfull parse" swap call
|
||||||
|
=> < lazy list >
|
||||||
|
(4) [ . ] leach
|
||||||
|
=> [ "begin" | " a successfull parse" ]
|
||||||
|
</pre>
|
||||||
|
<p>The word 'satisfy' takes a quotation from the top of the stack and
|
||||||
|
returns a parser than when called will call the quotation with the
|
||||||
|
first item in the input string on the stack. If the quotation returns
|
||||||
|
true then the parse is successful, otherwise it fails:</p>
|
||||||
|
<pre class="code">
|
||||||
|
(1) : digit-parser ( -- parser )
|
||||||
|
[ digit? ] satisfy ;
|
||||||
|
(2) "5" digit-parser call [ . ] leach
|
||||||
|
=> [ 53 | "" ]
|
||||||
|
(3) "a" digit-parser call
|
||||||
|
=> f
|
||||||
|
</pre>
|
||||||
|
<p>Note that 'digit-parser' returns a parser, it is not the parser
|
||||||
|
itself. It is really a parser generating word like 'token'. Whereas
|
||||||
|
our 'char-a' word defined originally was a parser itself.</p>
|
||||||
|
<p>Now that we can parse single digits it would be nice to easily
|
||||||
|
parse a string of them. The '<*>' parser combinator word will do
|
||||||
|
this. It accepts a parser on the top of the stack and produces a
|
||||||
|
parser that parses zero or more of the constructs that the original
|
||||||
|
parser parsed. The result of the '<*>' generated parser will be a list
|
||||||
|
list of the successful results returned by the original parser.</p>
|
||||||
|
<pre class="code">
|
||||||
|
(1) digit-parser <*>
|
||||||
|
=> < parser >
|
||||||
|
(2) "123" swap call
|
||||||
|
=> < lazy list >
|
||||||
|
(3) [ . ] leach
|
||||||
|
=> [ [ [ 49 50 51 ] ] | "" ]
|
||||||
|
[ [ [ 49 50 ] ] | "3" ]
|
||||||
|
[ [ [ 49 ] ] | "23" ]
|
||||||
|
[ f | "123" ]
|
||||||
|
</pre>
|
||||||
|
<p>In this case there are multiple successful parses. This is because
|
||||||
|
the occurrence of zero or more digits happens more than once. There is
|
||||||
|
also the 'f' case where zero digits is parsed. If only the 'longest
|
||||||
|
match' is required then the lcar of the lazy list can be used and the
|
||||||
|
remaining parse results are never produced.</p>
|
||||||
|
<p>The result of the parse above is the list of characters
|
||||||
|
parsed. Sometimes you want this to be something else, like an abstract
|
||||||
|
syntax tree, or some calculation. For the digit case we may want the
|
||||||
|
actual integer number.</p>
|
||||||
|
<p>For this we can use the '<@' parser
|
||||||
|
combinator. This combinator takes a parser and a quotation on the
|
||||||
|
stack and returns a new parser. When the new parser is called it will
|
||||||
|
call the original parser to produce the results, then it will call the
|
||||||
|
quotation on each successfull result, and the result of that quotation
|
||||||
|
will be the result of the parse:</p>
|
||||||
|
<pre class="code">
|
||||||
|
(1) : digit-parser2 ( -- parser )
|
||||||
|
[ digit? ] satisfy [ CHAR: 0 - ] <@ ;
|
||||||
|
(2) "5" digit-parser2 call [ . ] leach
|
||||||
|
=> [ 5 | "" ]
|
||||||
|
</pre>
|
||||||
|
<p>Notice that now the result is the actual integer '5' rather than
|
||||||
|
character code '53'.</p>
|
||||||
|
<pre class="code">
|
||||||
|
(1) : natural-parser ( -- parser )
|
||||||
|
digit-parser2 <*> [ car 0 [ swap 10 * + ] reduce unit ] <@ ;
|
||||||
|
(2) "123" natural-parser call
|
||||||
|
=> < lazy list >
|
||||||
|
(3) [ . ] leach
|
||||||
|
=> [ [ 123 ] | "" ]
|
||||||
|
[ [ 12 ] | "3" ]
|
||||||
|
[ [ 1 ] | "23" ]
|
||||||
|
[ f | "123" ]
|
||||||
|
</pre>
|
||||||
|
<p>The number parsed is the actual integer number due to the operation
|
||||||
|
of the '<@' word. This allows parsers to not only parse the input
|
||||||
|
string but perform operations and transformations on the syntax tree
|
||||||
|
returned.</p>
|
||||||
|
|
||||||
|
<p class="footer">
|
||||||
|
News and updates to this software can be obtained from the authors
|
||||||
|
weblog: <a href="http://radio.weblogs.com/0102385">Chris Double</a>.</p>
|
||||||
|
<p id="copyright">Copyright (c) 2004, Chris Double. All Rights Reserved.</p>
|
||||||
|
</body> </html>
|
|
@ -0,0 +1,28 @@
|
||||||
|
body { background: white; color: black; }
|
||||||
|
p { margin-left: 10%; margin-right: 10%;
|
||||||
|
font: normal 100% Verdana, Arial, Helvetica; }
|
||||||
|
td { margin-left: 10%; margin-right: 10%;
|
||||||
|
font: normal 100% Verdana, Arial, Helvetica; }
|
||||||
|
table { margin-left: 10%; margin-right: 10%; }
|
||||||
|
ul { margin-left: 10%; margin-right: 10%;
|
||||||
|
font: normal 100% Verdana, Arial, Helvetica; }
|
||||||
|
ol { margin-left: 10%; margin-right: 10%;
|
||||||
|
font: normal 100% Verdana, Arial, Helvetica; }
|
||||||
|
h1 { text-align: center; margin-bottom: 0; margin-top: 1em; }
|
||||||
|
h2 { margin: 0 5% 0 7.5%; font-size: 120%; font-style: italic; }
|
||||||
|
h3 { border: 2px solid blue; border-width: 2px 0.5em 2px 0.5em;
|
||||||
|
padding: 0.2em 0.2em 0.2em 0.5em; background: #fafafa;
|
||||||
|
margin-left: 10%; margin-right: 10%; margin-top: 2em;
|
||||||
|
font-size: 100%; }
|
||||||
|
.note { border: 2px solid blue; border-width: 2px 2px 2px 2em;
|
||||||
|
padding: 0.5em 0.5em 0.5em 1em; background: #ffe; }
|
||||||
|
.code { border: 1px solid black; border-width: 1px;
|
||||||
|
padding: 0.5em; background: #ffe;
|
||||||
|
margin-left: 10%; margin-right: 10%; }
|
||||||
|
blockquote { margin-left: 25%; margin-right: 25%;
|
||||||
|
font-style: italic; }
|
||||||
|
.highlite { color: red; }
|
||||||
|
.footer { margin-top: 2.5em; border-top: 1px solid gray; color:
|
||||||
|
#AAA; font-size: 85%; padding-top: 0.33em; }
|
||||||
|
#copyright { text-align: center; color: #AAA;
|
||||||
|
font-size: 65%; }
|
Loading…
Reference in New Issue