Tested each sample.

This commit is contained in:
Diego Nehab 2007-10-11 21:16:28 +00:00
parent e394956cde
commit 52ac60af81
27 changed files with 789 additions and 163 deletions

4
gem/ex1.lua Normal file
View File

@ -0,0 +1,4 @@
local CRLF = "\013\010"
local input = source.chain(source.file(io.stdin), normalize(CRLF))
local output = sink.file(io.stdout)
pump.all(input, output)

17
gem/ex10.lua Normal file
View File

@ -0,0 +1,17 @@
function pump.step(src, snk)
local chunk, src_err = src()
local ret, snk_err = snk(chunk, src_err)
if chunk and ret then return 1
else return nil, src_err or snk_err end
end
function pump.all(src, snk, step)
step = step or pump.step
while true do
local ret, err = step(src, snk)
if not ret then
if err then return nil, err
else return 1 end
end
end
end

7
gem/ex11.lua Normal file
View File

@ -0,0 +1,7 @@
local input = source.chain(
source.file(io.open("input.bin", "rb")),
encode("base64"))
local output = sink.chain(
wrap(76),
sink.file(io.open("output.b64", "w")))
pump.all(input, output)

34
gem/ex12.lua Normal file
View File

@ -0,0 +1,34 @@
local smtp = require"socket.smtp"
local mime = require"mime"
local ltn12 = require"ltn12"
CRLF = "\013\010"
local message = smtp.message{
headers = {
from = "Sicrano <sicrano@example.com>",
to = "Fulano <fulano@example.com>",
subject = "A message with an attachment"},
body = {
preamble = "Hope you can see the attachment" .. CRLF,
[1] = {
body = "Here is our logo" .. CRLF},
[2] = {
headers = {
["content-type"] = 'image/png; name="luasocket.png"',
["content-disposition"] =
'attachment; filename="luasocket.png"',
["content-description"] = 'LuaSocket logo',
["content-transfer-encoding"] = "BASE64"},
body = ltn12.source.chain(
ltn12.source.file(io.open("luasocket.png", "rb")),
ltn12.filter.chain(
mime.encode("base64"),
mime.wrap()))}}}
assert(smtp.send{
rcpt = "<diego@cs.princeton.edu>",
from = "<diego@cs.princeton.edu>",
server = "localhost",
port = 2525,
source = message})

11
gem/ex2.lua Normal file
View File

@ -0,0 +1,11 @@
function filter.cycle(lowlevel, context, extra)
return function(chunk)
local ret
ret, context = lowlevel(context, chunk, extra)
return ret
end
end
function normalize(marker)
return filter.cycle(eol, 0, marker)
end

15
gem/ex3.lua Normal file
View File

@ -0,0 +1,15 @@
local function chainpair(f1, f2)
return function(chunk)
local ret = f2(f1(chunk))
if chunk then return ret
else return (ret or "") .. (f2() or "") end
end
end
function filter.chain(...)
local f = select(1, ...)
for i = 2, select('#', ...) do
f = chainpair(f, select(i, ...))
end
return f
end

5
gem/ex4.lua Normal file
View File

@ -0,0 +1,5 @@
local qp = filter.chain(normalize(CRLF), encode("quoted-printable"),
wrap("quoted-printable"))
local input = source.chain(source.file(io.stdin), qp)
local output = sink.file(io.stdout)
pump.all(input, output)

15
gem/ex5.lua Normal file
View File

@ -0,0 +1,15 @@
function source.empty(err)
return function()
return nil, err
end
end
function source.file(handle, io_err)
if handle then
return function()
local chunk = handle:read(20)
if not chunk then handle:close() end
return chunk
end
else return source.empty(io_err or "unable to open file") end
end

14
gem/ex6.lua Normal file
View File

@ -0,0 +1,14 @@
function source.chain(src, f)
return function()
if not src then
return nil
end
local chunk, err = src()
if not chunk then
src = nil
return f(nil)
else
return f(chunk)
end
end
end

16
gem/ex7.lua Normal file
View File

@ -0,0 +1,16 @@
function sink.table(t)
t = t or {}
local f = function(chunk, err)
if chunk then table.insert(t, chunk) end
return 1
end
return f, t
end
local function null()
return 1
end
function sink.null()
return null
end

5
gem/ex8.lua Normal file
View File

@ -0,0 +1,5 @@
local input = source.file(io.stdin)
local output, t = sink.table()
output = sink.chain(normalize(CRLF), output)
pump.all(input, output)
io.write(table.concat(t))

3
gem/ex9.lua Normal file
View File

@ -0,0 +1,3 @@
for chunk in source.file(io.stdin) do
io.write(chunk)
end

54
gem/gem.c Normal file
View File

@ -0,0 +1,54 @@
#include "lua.h"
#include "lauxlib.h"
#define CR '\xD'
#define LF '\xA'
#define CRLF "\xD\xA"
#define candidate(c) (c == CR || c == LF)
static int pushchar(int c, int last, const char *marker,
luaL_Buffer *buffer) {
if (candidate(c)) {
if (candidate(last)) {
if (c == last)
luaL_addstring(buffer, marker);
return 0;
} else {
luaL_addstring(buffer, marker);
return c;
}
} else {
luaL_putchar(buffer, c);
return 0;
}
}
static int eol(lua_State *L) {
int context = luaL_checkint(L, 1);
size_t isize = 0;
const char *input = luaL_optlstring(L, 2, NULL, &isize);
const char *last = input + isize;
const char *marker = luaL_optstring(L, 3, CRLF);
luaL_Buffer buffer;
luaL_buffinit(L, &buffer);
if (!input) {
lua_pushnil(L);
lua_pushnumber(L, 0);
return 2;
}
while (input < last)
context = pushchar(*input++, context, marker, &buffer);
luaL_pushresult(&buffer);
lua_pushnumber(L, context);
return 2;
}
static luaL_reg func[] = {
{ "eol", eol },
{ NULL, NULL }
};
int luaopen_gem(lua_State *L) {
luaL_openlib(L, "gem", func, 0);
return 0;
}

206
gem/gt.b64 Normal file
View File

@ -0,0 +1,206 @@
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

BIN
gem/input.bin Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -6,7 +6,10 @@
\DefineVerbatimEnvironment{mime}{Verbatim}{fontsize=\small,commandchars=\$\#\%} \DefineVerbatimEnvironment{mime}{Verbatim}{fontsize=\small,commandchars=\$\#\%}
\newcommand{\stick}[1]{\vbox{\setlength{\parskip}{0pt}#1}} \newcommand{\stick}[1]{\vbox{\setlength{\parskip}{0pt}#1}}
\newcommand{\bl}{\ensuremath{\mathtt{\backslash}}} \newcommand{\bl}{\ensuremath{\mathtt{\backslash}}}
\newcommand{\CR}{\texttt{CR}}
\newcommand{\LF}{\texttt{LF}}
\newcommand{\CRLF}{\texttt{CR~LF}}
\newcommand{\nil}{\texttt{nil}}
\title{Filters, sources, sinks, and pumps\\ \title{Filters, sources, sinks, and pumps\\
{\large or Functional programming for the rest of us}} {\large or Functional programming for the rest of us}}
@ -18,29 +21,30 @@
\begin{abstract} \begin{abstract}
Certain data processing operations can be implemented in the Certain data processing operations can be implemented in the
form of filters. A filter is a function that can process data form of filters. A filter is a function that can process
received in consecutive function calls, returning partial data received in consecutive invocations, returning partial
results after each invocation. Examples of operations that can be results each time it is called. Examples of operations that
implemented as filters include the end-of-line normalization can be implemented as filters include the end-of-line
for text, Base64 and Quoted-Printable transfer content normalization for text, Base64 and Quoted-Printable transfer
encodings, the breaking of text into lines, SMTP dot-stuffing, content encodings, the breaking of text into lines, SMTP
and there are many others. Filters become even dot-stuffing, and there are many others. Filters become
more powerful when we allow them to be chained together to even more powerful when we allow them to be chained together
create composite filters. In this context, filters can be seen to create composite filters. In this context, filters can be
as the middle links in a chain of data transformations. Sources an sinks seen as the internal links in a chain of data transformations.
are the corresponding end points of these chains. A source Sources and sinks are the corresponding end points in these
is a function that produces data, chunk by chunk, and a sink chains. A source is a function that produces data, chunk by
is a function that takes data, chunk by chunk. In this chunk, and a sink is a function that takes data, chunk by
article, we describe the design of an elegant interface for filters, chunk. Finally, pumps are procedures that actively drive
sources, sinks, and chaining, and illustrate each step data from a source to a sink, and indirectly through all
with concrete examples. intervening filters. In this article, we describe the design of an
elegant interface for filters, sources, sinks, chains, and
pumps, and we illustrate each step with concrete examples.
\end{abstract} \end{abstract}
\section{Introduction} \section{Introduction}
Within the realm of networking applications, we are often Within the realm of networking applications, we are often
required apply transformations to streams of data. Examples required to apply transformations to streams of data. Examples
include the end-of-line normalization for text, Base64 and include the end-of-line normalization for text, Base64 and
Quoted-Printable transfer content encodings, breaking text Quoted-Printable transfer content encodings, breaking text
into lines with a maximum number of columns, SMTP into lines with a maximum number of columns, SMTP
@ -50,11 +54,10 @@ transfer coding, and the list goes on.
Many complex tasks require a combination of two or more such Many complex tasks require a combination of two or more such
transformations, and therefore a general mechanism for transformations, and therefore a general mechanism for
promoting reuse is desirable. In the process of designing promoting reuse is desirable. In the process of designing
\texttt{LuaSocket~2.0}, David Burgess and I were forced to deal with \texttt{LuaSocket~2.0}, we repeatedly faced this problem.
this problem. The solution we reached proved to be very The solution we reached proved to be very general and
general and convenient. It is based on the concepts of convenient. It is based on the concepts of filters, sources,
filters, sources, sinks, and pumps, which we introduce sinks, and pumps, which we introduce below.
below.
\emph{Filters} are functions that can be repeatedly invoked \emph{Filters} are functions that can be repeatedly invoked
with chunks of input, successively returning processed with chunks of input, successively returning processed
@ -62,34 +65,33 @@ chunks of output. More importantly, the result of
concatenating all the output chunks must be the same as the concatenating all the output chunks must be the same as the
result of applying the filter to the concatenation of all result of applying the filter to the concatenation of all
input chunks. In fancier language, filters \emph{commute} input chunks. In fancier language, filters \emph{commute}
with the concatenation operator. As a result, chunk with the concatenation operator. More importantly, filters
boundaries are irrelevant: filters correctly handle input must handle input data correctly no matter how the stream
data no matter how it is split. has been split into chunks.
A \emph{chain} transparently combines the effect of one or A \emph{chain} is a function that transparently combines the
more filters. The interface of a chain is effect of one or more filters. The interface of a chain is
indistinguishable from the interface of its components. indistinguishable from the interface of its component
This allows a chained filter to be used wherever an atomic filters. This allows a chained filter to be used wherever
filter is expected. In particular, chains can be an atomic filter is accepted. In particular, chains can be
themselves chained to create arbitrarily complex operations. themselves chained to create arbitrarily complex operations.
Filters can be seen as internal nodes in a network through Filters can be seen as internal nodes in a network through
which data will flow, potentially being transformed many which data will flow, potentially being transformed many
times along its way. Chains connect these nodes together. times along the way. Chains connect these nodes together.
To complete the picture, we need \emph{sources} and The initial and final nodes of the network are
\emph{sinks}. These are the initial and final nodes of the \emph{sources} and \emph{sinks}, respectively. Less
network, respectively. Less abstractly, a source is a abstractly, a source is a function that produces new data
function that produces new data every time it is called. every time it is invoked. Conversely, sinks are functions
Conversely, sinks are functions that give a final that give a final destination to the data they receive.
destination to the data they receive. Naturally, sources Naturally, sources and sinks can also be chained with
and sinks can also be chained with filters to produce filters to produce filtered sources and sinks.
filtered sources and sinks.
Finally, filters, chains, sources, and sinks are all passive Finally, filters, chains, sources, and sinks are all passive
entities: they must be repeatedly invoked in order for entities: they must be repeatedly invoked in order for
anything to happen. \emph{Pumps} provide the driving force anything to happen. \emph{Pumps} provide the driving force
that pushes data through the network, from a source to a that pushes data through the network, from a source to a
sink. sink, and indirectly through all intervening filters.
In the following sections, we start with a simplified In the following sections, we start with a simplified
interface, which we later refine. The evolution we present interface, which we later refine. The evolution we present
@ -99,27 +101,28 @@ concepts within our application domain.
\subsection{A simple example} \subsection{A simple example}
Let us use the end-of-line normalization of text as an The end-of-line normalization of text is a good
example to motivate our initial filter interface. example to motivate our initial filter interface.
Assume we are given text in an unknown end-of-line Assume we are given text in an unknown end-of-line
convention (including possibly mixed conventions) out of the convention (including possibly mixed conventions) out of the
commonly found Unix (LF), Mac OS (CR), and DOS (CRLF) commonly found Unix (\LF), Mac OS (\CR), and
conventions. We would like to be able to write code like the DOS (\CRLF) conventions. We would like to be able to
following: use the folowing code to normalize the end-of-line markers:
\begin{quote} \begin{quote}
\begin{lua} \begin{lua}
@stick# @stick#
local in = source.chain(source.file(io.stdin), normalize("\r\n")) local CRLF = "\013\010"
local out = sink.file(io.stdout) local input = source.chain(source.file(io.stdin), normalize(CRLF))
pump.all(in, out) local output = sink.file(io.stdout)
pump.all(input, output)
% %
\end{lua} \end{lua}
\end{quote} \end{quote}
This program should read data from the standard input stream This program should read data from the standard input stream
and normalize the end-of-line markers to the canonic CRLF and normalize the end-of-line markers to the canonic
marker, as defined by the MIME standard. Finally, the \CRLF\ marker, as defined by the MIME standard.
normalized text should be sent to the standard output Finally, the normalized text should be sent to the standard output
stream. We use a \emph{file source} that produces data from stream. We use a \emph{file source} that produces data from
standard input, and chain it with a filter that normalizes standard input, and chain it with a filter that normalizes
the data. The pump then repeatedly obtains data from the the data. The pump then repeatedly obtains data from the
@ -127,27 +130,28 @@ source, and passes it to the \emph{file sink}, which sends
it to the standard output. it to the standard output.
In the code above, the \texttt{normalize} \emph{factory} is a In the code above, the \texttt{normalize} \emph{factory} is a
function that creates our normalization filter. This filter function that creates our normalization filter, which
will replace any end-of-line marker with the canonic replaces any end-of-line marker with the canonic marker.
`\verb|\r\n|' marker. The initial filter interface is The initial filter interface is
trivial: a filter function receives a chunk of input data, trivial: a filter function receives a chunk of input data,
and returns a chunk of processed data. When there are no and returns a chunk of processed data. When there are no
more input data left, the caller notifies the filter by invoking more input data left, the caller notifies the filter by invoking
it with a \texttt{nil} chunk. The filter responds by returning it with a \nil\ chunk. The filter responds by returning
the final chunk of processed data. the final chunk of processed data (which could of course be
the empty string).
Although the interface is extremely simple, the Although the interface is extremely simple, the
implementation is not so obvious. A normalization filter implementation is not so obvious. A normalization filter
respecting this interface needs to keep some kind of context respecting this interface needs to keep some kind of context
between calls. This is because a chunk boundary may lie between between calls. This is because a chunk boundary may lie between
the CR and LF characters marking the end of a line. This the \CR\ and \LF\ characters marking the end of a single line. This
need for contextual storage motivates the use of need for contextual storage motivates the use of
factories: each time the factory is invoked, it returns a factories: each time the factory is invoked, it returns a
filter with its own context so that we can have several filter with its own context so that we can have several
independent filters being used at the same time. For independent filters being used at the same time. For
efficiency reasons, we must avoid the obvious solution of efficiency reasons, we must avoid the obvious solution of
concatenating all the input into the context before concatenating all the input into the context before
producing any output. producing any output chunks.
To that end, we break the implementation into two parts: To that end, we break the implementation into two parts:
a low-level filter, and a factory of high-level filters. The a low-level filter, and a factory of high-level filters. The
@ -167,10 +171,10 @@ end-of-line normalization filters:
\begin{quote} \begin{quote}
\begin{lua} \begin{lua}
@stick# @stick#
function filter.cycle(low, ctx, extra) function filter.cycle(lowlevel, context, extra)
return function(chunk) return function(chunk)
local ret local ret
ret, ctx = low(ctx, chunk, extra) ret, context = lowlevel(context, chunk, extra)
return ret return ret
end end
end end
@ -178,27 +182,30 @@ end
@stick# @stick#
function normalize(marker) function normalize(marker)
return cycle(eol, 0, marker) return filter.cycle(eol, 0, marker)
end end
% %
\end{lua} \end{lua}
\end{quote} \end{quote}
The \texttt{normalize} factory simply calls a more generic The \texttt{normalize} factory simply calls a more generic
factory, the \texttt{cycle} factory. This factory receives a factory, the \texttt{cycle}~factory, passing the low-level
filter~\texttt{eol}. The \texttt{cycle}~factory receives a
low-level filter, an initial context, and an extra low-level filter, an initial context, and an extra
parameter, and returns a new high-level filter. Each time parameter, and returns a new high-level filter. Each time
the high-level filer is passed a new chunk, it invokes the the high-level filer is passed a new chunk, it invokes the
low-level filter with the previous context, the new chunk, low-level filter with the previous context, the new chunk,
and the extra argument. It is the low-level filter that and the extra argument. It is the low-level filter that
does all the work, producing the chunk of processed data and does all the work, producing the chunk of processed data and
a new context. The high-level filter then updates its a new context. The high-level filter then replaces its
internal context, and returns the processed chunk of data to internal context, and returns the processed chunk of data to
the user. Notice that we take advantage of Lua's lexical the user. Notice that we take advantage of Lua's lexical
scoping to store the context in a closure between function scoping to store the context in a closure between function
calls. calls.
Concerning the low-level filter code, we must first accept \subsection{The C part of the filter}
As for the low-level filter, we must first accept
that there is no perfect solution to the end-of-line marker that there is no perfect solution to the end-of-line marker
normalization problem. The difficulty comes from an normalization problem. The difficulty comes from an
inherent ambiguity in the definition of empty lines within inherent ambiguity in the definition of empty lines within
@ -208,39 +215,39 @@ mixed input. It also does a reasonable job with empty lines
and serves as a good example of how to implement a low-level and serves as a good example of how to implement a low-level
filter. filter.
The idea is to consider both CR and~LF as end-of-line The idea is to consider both \CR\ and~\LF\ as end-of-line
\emph{candidates}. We issue a single break if any candidate \emph{candidates}. We issue a single break if any candidate
is seen alone, or followed by a different candidate. In is seen alone, or if it is followed by a different
other words, CR~CR~and LF~LF each issue two end-of-line candidate. In other words, \CR~\CR~and \LF~\LF\ each issue
markers, whereas CR~LF~and LF~CR issue only one marker each. two end-of-line markers, whereas \CR~\LF~and \LF~\CR\ issue
This method correctly handles the Unix, DOS/MIME, VMS, and Mac only one marker each. It is easy to see that this method
OS conventions. correctly handles the most common end-of-line conventions.
\subsection{The C part of the filter} With this in mind, we divide the low-level filter into two
simple functions. The inner function~\texttt{pushchar} performs the
Our low-level filter is divided into two simple functions. normalization itself. It takes each input character in turn,
The inner function performs the normalization itself. It takes deciding what to output and how to modify the context. The
each input character in turn, deciding what to output and context tells if the last processed character was an
how to modify the context. The context tells if the last end-of-line candidate, and if so, which candidate it was.
processed character was an end-of-line candidate, and if so, For efficiency, we use Lua's auxiliary library's buffer
which candidate it was. For efficiency, it uses interface:
Lua's auxiliary library's buffer interface:
\begin{quote} \begin{quote}
\begin{C} \begin{C}
@stick# @stick#
@#define candidate(c) (c == CR || c == LF) @#define candidate(c) (c == CR || c == LF)
static int process(int c, int last, const char *marker, static int pushchar(int c, int last, const char *marker,
luaL_Buffer *buffer) { luaL_Buffer *buffer) {
if (candidate(c)) { if (candidate(c)) {
if (candidate(last)) { if (candidate(last)) {
if (c == last) luaL_addstring(buffer, marker); if (c == last)
luaL_addstring(buffer, marker);
return 0; return 0;
} else { } else {
luaL_addstring(buffer, marker); luaL_addstring(buffer, marker);
return c; return c;
} }
} else { } else {
luaL_putchar(buffer, c); luaL_pushchar(buffer, c);
return 0; return 0;
} }
} }
@ -248,15 +255,20 @@ static int process(int c, int last, const char *marker,
\end{C} \end{C}
\end{quote} \end{quote}
The outer function simply interfaces with Lua. It receives the The outer function~\texttt{eol} simply interfaces with Lua.
context and input chunk (as well as an optional It receives the context and input chunk (as well as an
custom end-of-line marker), and returns the transformed optional custom end-of-line marker), and returns the
output chunk and the new context: transformed output chunk and the new context.
Notice that if the input chunk is \nil, the operation
is considered to be finished. In that case, the loop will
not execute a single time and the context is reset to the
initial state. This allows the filter to be reused many
times:
\begin{quote} \begin{quote}
\begin{C} \begin{C}
@stick# @stick#
static int eol(lua_State *L) { static int eol(lua_State *L) {
int ctx = luaL_checkint(L, 1); int context = luaL_checkint(L, 1);
size_t isize = 0; size_t isize = 0;
const char *input = luaL_optlstring(L, 2, NULL, &isize); const char *input = luaL_optlstring(L, 2, NULL, &isize);
const char *last = input + isize; const char *last = input + isize;
@ -269,24 +281,18 @@ static int eol(lua_State *L) {
return 2; return 2;
} }
while (input < last) while (input < last)
ctx = process(*input++, ctx, marker, &buffer); context = pushchar(*input++, context, marker, &buffer);
luaL_pushresult(&buffer); luaL_pushresult(&buffer);
lua_pushnumber(L, ctx); lua_pushnumber(L, context);
return 2; return 2;
} }
% %
\end{C} \end{C}
\end{quote} \end{quote}
Notice that if the input chunk is \texttt{nil}, the operation
is considered to be finished. In that case, the loop will
not execute a single time and the context is reset to the
initial state. This allows the filter to be reused many
times.
When designing your own filters, the challenging part is to When designing your own filters, the challenging part is to
decide what will be in the context. For line breaking, for decide what will be in the context. For line breaking, for
instance, it could be the number of bytes left in the instance, it could be the number of bytes that still fit in the
current line. For Base64 encoding, it could be a string current line. For Base64 encoding, it could be a string
with the bytes that remain after the division of the input with the bytes that remain after the division of the input
into 3-byte atoms. The MIME module in the \texttt{LuaSocket} into 3-byte atoms. The MIME module in the \texttt{LuaSocket}
@ -294,19 +300,22 @@ distribution has many other examples.
\section{Filter chains} \section{Filter chains}
Chains add a lot to the power of filters. For example, Chains greatly increase the power of filters. For example,
according to the standard for Quoted-Printable encoding, according to the standard for Quoted-Printable encoding,
text must be normalized to a canonic end-of-line marker text should be normalized to a canonic end-of-line marker
prior to encoding. To help specifying complex prior to encoding. After encoding, the resulting text must
transformations like this, we define a chain factory that be broken into lines of no more than 76 characters, with the
creates a composite filter from one or more filters. A use of soft line breaks (a line terminated by the \texttt{=}
chained filter passes data through all its components, and sign). To help specifying complex transformations like
can be used wherever a primitive filter is accepted. this, we define a chain factory that creates a composite
filter from one or more filters. A chained filter passes
data through all its components, and can be used wherever a
primitive filter is accepted.
The chaining factory is very simple. The auxiliary The chaining factory is very simple. The auxiliary
function~\texttt{chainpair} chains two filters together, function~\texttt{chainpair} chains two filters together,
taking special care if the chunk is the last. This is taking special care if the chunk is the last. This is
because the final \texttt{nil} chunk notification has to be because the final \nil\ chunk notification has to be
pushed through both filters in turn: pushed through both filters in turn:
\begin{quote} \begin{quote}
\begin{lua} \begin{lua}
@ -322,9 +331,9 @@ end
@stick# @stick#
function filter.chain(...) function filter.chain(...)
local f = arg[1] local f = select(1, ...)
for i = 2, @#arg do for i = 2, select('@#', ...) do
f = chainpair(f, arg[i]) f = chainpair(f, select(i, ...))
end end
return f return f
end end
@ -337,11 +346,11 @@ define the Quoted-Printable conversion as such:
\begin{quote} \begin{quote}
\begin{lua} \begin{lua}
@stick# @stick#
local qp = filter.chain(normalize("\r\n"), local qp = filter.chain(normalize(CRLF), encode("quoted-printable"),
encode("quoted-printable")) wrap("quoted-printable"))
local in = source.chain(source.file(io.stdin), qp) local input = source.chain(source.file(io.stdin), qp)
local out = sink.file(io.stdout) local output = sink.file(io.stdout)
pump.all(in, out) pump.all(input, output)
% %
\end{lua} \end{lua}
\end{quote} \end{quote}
@ -360,14 +369,14 @@ gives a final destination to the data.
\subsection{Sources} \subsection{Sources}
A source returns the next chunk of data each time it is A source returns the next chunk of data each time it is
invoked. When there is no more data, it simply returns invoked. When there is no more data, it simply returns~\nil.
\texttt{nil}. In the event of an error, the source can inform the In the event of an error, the source can inform the
caller by returning \texttt{nil} followed by an error message. caller by returning \nil\ followed by the error message.
Below are two simple source factories. The \texttt{empty} source Below are two simple source factories. The \texttt{empty} source
returns no data, possibly returning an associated error returns no data, possibly returning an associated error
message. The \texttt{file} source works harder, and message. The \texttt{file} source yields the contents of a file
yields the contents of a file in a chunk by chunk fashion: in a chunk by chunk fashion:
\begin{quote} \begin{quote}
\begin{lua} \begin{lua}
@stick# @stick#
@ -398,7 +407,7 @@ A filtered source passes its data through the
associated filter before returning it to the caller. associated filter before returning it to the caller.
Filtered sources are useful when working with Filtered sources are useful when working with
functions that get their input data from a source (such as functions that get their input data from a source (such as
the pump in our first example). By chaining a source with one or the pumps in our examples). By chaining a source with one or
more filters, the function can be transparently provided more filters, the function can be transparently provided
with filtered data, with no need to change its interface. with filtered data, with no need to change its interface.
Here is a factory that does the job: Here is a factory that does the job:
@ -406,14 +415,18 @@ Here is a factory that does the job:
\begin{lua} \begin{lua}
@stick# @stick#
function source.chain(src, f) function source.chain(src, f)
return source.simplify(function() return function()
if not src then return nil end if not src then
return nil
end
local chunk, err = src() local chunk, err = src()
if not chunk then if not chunk then
src = nil src = nil
return f(nil) return f(nil)
else return f(chunk) end else
end) return f(chunk)
end
end
end end
% %
\end{lua} \end{lua}
@ -421,20 +434,20 @@ end
\subsection{Sinks} \subsection{Sinks}
Just as we defined an interface a data source, Just as we defined an interface for source of data,
we can also define an interface for a data destination. we can also define an interface for a data destination.
We call any function respecting this We call any function respecting this
interface a \emph{sink}. In our first example, we used a interface a \emph{sink}. In our first example, we used a
file sink connected to the standard output. file sink connected to the standard output.
Sinks receive consecutive chunks of data, until the end of Sinks receive consecutive chunks of data, until the end of
data is signaled by a \texttt{nil} chunk. A sink can be data is signaled by a \nil\ input chunk. A sink can be
notified of an error with an optional extra argument that notified of an error with an optional extra argument that
contains the error message, following a \texttt{nil} chunk. contains the error message, following a \nil\ chunk.
If a sink detects an error itself, and If a sink detects an error itself, and
wishes not to be called again, it can return \texttt{nil}, wishes not to be called again, it can return \nil,
followed by an error message. A return value that followed by an error message. A return value that
is not \texttt{nil} means the source will accept more data. is not \nil\ means the sink will accept more data.
Below are two useful sink factories. Below are two useful sink factories.
The table factory creates a sink that stores The table factory creates a sink that stores
@ -469,7 +482,7 @@ end
Naturally, filtered sinks are just as useful as filtered Naturally, filtered sinks are just as useful as filtered
sources. A filtered sink passes each chunk it receives sources. A filtered sink passes each chunk it receives
through the associated filter before handing it to the through the associated filter before handing it down to the
original sink. In the following example, we use a source original sink. In the following example, we use a source
that reads from the standard input. The input chunks are that reads from the standard input. The input chunks are
sent to a table sink, which has been coupled with a sent to a table sink, which has been coupled with a
@ -479,10 +492,10 @@ standard out:
\begin{quote} \begin{quote}
\begin{lua} \begin{lua}
@stick# @stick#
local in = source.file(io.stdin) local input = source.file(io.stdin)
local out, t = sink.table() local output, t = sink.table()
out = sink.chain(normalize("\r\n"), out) output = sink.chain(normalize(CRLF), output)
pump.all(in, out) pump.all(input, output)
io.write(table.concat(t)) io.write(table.concat(t))
% %
\end{lua} \end{lua}
@ -490,11 +503,11 @@ io.write(table.concat(t))
\subsection{Pumps} \subsection{Pumps}
Adrian Sietsma noticed that, although not on purpose, our Although not on purpose, our interface for sources is
interface for sources is compatible with Lua iterators. compatible with Lua iterators. That is, a source can be
That is, a source can be neatly used in conjunction neatly used in conjunction with \texttt{for} loops. Using
with \texttt{for} loops. Using our file our file source as an iterator, we can write the following
source as an iterator, we can write the following code: code:
\begin{quote} \begin{quote}
\begin{lua} \begin{lua}
@stick# @stick#
@ -539,20 +552,22 @@ end
The \texttt{pump.step} function moves one chunk of data from The \texttt{pump.step} function moves one chunk of data from
the source to the sink. The \texttt{pump.all} function takes the source to the sink. The \texttt{pump.all} function takes
an optional \texttt{step} function and uses it to pump all the an optional \texttt{step} function and uses it to pump all the
data from the source to the sink. We can now use everything data from the source to the sink.
we have to write a program that reads a binary file from Here is an example that uses the Base64 and the
line wrapping filters from the \texttt{LuaSocket}
distribution. The program reads a binary file from
disk and stores it in another file, after encoding it to the disk and stores it in another file, after encoding it to the
Base64 transfer content encoding: Base64 transfer content encoding:
\begin{quote} \begin{quote}
\begin{lua} \begin{lua}
@stick# @stick#
local in = source.chain( local input = source.chain(
source.file(io.open("input.bin", "rb")), source.file(io.open("input.bin", "rb")),
encode("base64")) encode("base64"))
local out = sink.chain( local output = sink.chain(
wrap(76), wrap(76),
sink.file(io.open("output.b64", "w"))) sink.file(io.open("output.b64", "w")))
pump.all(in, out) pump.all(input, output)
% %
\end{lua} \end{lua}
\end{quote} \end{quote}
@ -561,19 +576,17 @@ The way we split the filters here is not intuitive, on
purpose. Alternatively, we could have chained the Base64 purpose. Alternatively, we could have chained the Base64
encode filter and the line-wrap filter together, and then encode filter and the line-wrap filter together, and then
chain the resulting filter with either the file source or chain the resulting filter with either the file source or
the file sink. It doesn't really matter. The Base64 and the the file sink. It doesn't really matter.
line wrapping filters are part of the \texttt{LuaSocket}
distribution.
\section{Exploding filters} \section{Exploding filters}
Our current filter interface has one flagrant shortcoming. Our current filter interface has one serious shortcoming.
When David Burgess was writing his \texttt{gzip} filter, he Consider for example a \texttt{gzip} decompression filter.
noticed that a decompression filter can explode a small During decompression, a small input chunk can be exploded
input chunk into a huge amount of data. To address this into a huge amount of data. To address this problem, we
problem, we decided to change the filter interface and allow decided to change the filter interface and allow exploding
exploding filters to return large quantities of output data filters to return large quantities of output data in a chunk
in a chunk by chunk manner. by chunk manner.
More specifically, after passing each chunk of input to More specifically, after passing each chunk of input to
a filter, and collecting the first chunk of output, the a filter, and collecting the first chunk of output, the
@ -582,11 +595,11 @@ filtered data is left. Within these secondary calls, the
caller passes an empty string to the filter. The filter caller passes an empty string to the filter. The filter
responds with an empty string when it is ready for the next responds with an empty string when it is ready for the next
input chunk. In the end, after the user passes a input chunk. In the end, after the user passes a
\texttt{nil} chunk notifying the filter that there is no \nil\ chunk notifying the filter that there is no
more input data, the filter might still have to produce too more input data, the filter might still have to produce too
much output data to return in a single chunk. The user has much output data to return in a single chunk. The user has
to loop again, now passing \texttt{nil} to the filter each time, to loop again, now passing \nil\ to the filter each time,
until the filter itself returns \texttt{nil} to notify the until the filter itself returns \nil\ to notify the
user it is finally done. user it is finally done.
Fortunately, it is very easy to modify a filter to respect Fortunately, it is very easy to modify a filter to respect
@ -604,8 +617,8 @@ filters practical.
\section{A complex example} \section{A complex example}
The LTN12 module in the \texttt{LuaSocket} distribution The LTN12 module in the \texttt{LuaSocket} distribution
implements the ideas we have described. The MIME implements all the ideas we have described. The MIME
and SMTP modules are especially integrated with LTN12, and SMTP modules are tightly integrated with LTN12,
and can be used to showcase the expressive power of filters, and can be used to showcase the expressive power of filters,
sources, sinks, and pumps. Below is an example sources, sinks, and pumps. Below is an example
of how a user would proceed to define and send a of how a user would proceed to define and send a
@ -622,9 +635,9 @@ local message = smtp.message{
to = "Fulano <fulano@example.com>", to = "Fulano <fulano@example.com>",
subject = "A message with an attachment"}, subject = "A message with an attachment"},
body = { body = {
preamble = "Hope you can see the attachment\r\n", preamble = "Hope you can see the attachment" .. CRLF,
[1] = { [1] = {
body = "Here is our logo\r\n"}, body = "Here is our logo" .. CRLF},
[2] = { [2] = {
headers = { headers = {
["content-type"] = 'image/png; name="luasocket.png"', ["content-type"] = 'image/png; name="luasocket.png"',
@ -665,6 +678,18 @@ abstraction for final data destinations. Filters define an
interface for data transformations. The chaining of interface for data transformations. The chaining of
filters, sources and sinks provides an elegant way to create filters, sources and sinks provides an elegant way to create
arbitrarily complex data transformations from simpler arbitrarily complex data transformations from simpler
components. Pumps simply move the data through. components. Pumps simply push the data through.
\section{Acknowledgements}
The concepts described in this text are the result of long
discussions with David Burgess. A version of this text has
been released on-line as the Lua Technical Note 012, hence
the name of the corresponding LuaSocket module,
\texttt{ltn12}. Wim Couwenberg contributed to the
implementation of the module, and Adrian Sietsma was the
first to notice the correspondence between sources and Lua
iterators.
\end{document} \end{document}

BIN
gem/luasocket.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -12,3 +12,12 @@ clean:
pdf: ltn012.pdf pdf: ltn012.pdf
open ltn012.pdf open ltn012.pdf
test: gem.so
gem.o: gem.c
gcc -c -o gem.o -Wall -ansi -W -O2 gem.c
gem.so: gem.o
export MACOSX_DEPLOYMENT_TARGET="10.3"; gcc -bundle -undefined dynamic_lookup -o gem.so gem.o

25
gem/t1.lua Normal file
View File

@ -0,0 +1,25 @@
source = {}
sink = {}
pump = {}
filter = {}
-- source.chain
dofile("ex6.lua")
-- source.file
dofile("ex5.lua")
-- normalize
require"gem"
eol = gem.eol
dofile("ex2.lua")
-- sink.file
require"ltn12"
sink.file = ltn12.sink.file
-- pump.all
dofile("ex10.lua")
-- run test
dofile("ex1.lua")

5
gem/t1lf.txt Normal file
View File

@ -0,0 +1,5 @@
this is a test file
it should have been saved as lf eol
but t1.lua will convert it to crlf eol
otherwise it is broken!

36
gem/t2.lua Normal file
View File

@ -0,0 +1,36 @@
source = {}
sink = {}
pump = {}
filter = {}
-- filter.chain
dofile("ex3.lua")
-- normalize
require"gem"
eol = gem.eol
dofile("ex2.lua")
-- encode
require"mime"
encode = mime.encode
-- wrap
wrap = mime.wrap
-- source.chain
dofile("ex6.lua")
-- source.file
dofile("ex5.lua")
-- sink.file
require"ltn12"
sink.file = ltn12.sink.file
-- pump.all
dofile("ex10.lua")
-- run test
CRLF = "\013\010"
dofile("ex4.lua")

4
gem/t2.txt Normal file
View File

@ -0,0 +1,4 @@
esse é um texto com acentos
quoted-printable tem que quebrar linhas longas, com mais que 76 linhas de texto
fora que as quebras de linhas têm que ser normalizadas
vamos ver o que dá isso aqui

5
gem/t2gt.qp Normal file
View File

@ -0,0 +1,5 @@
esse =E9 um texto com acentos
quoted-printable tem que quebrar linhas longas, com mais que 76 linhas de t=
exto
fora que as quebras de linhas t=EAm que ser normalizadas
vamos ver o que d=E1 isso aqui

25
gem/t3.lua Normal file
View File

@ -0,0 +1,25 @@
source = {}
sink = {}
pump = {}
filter = {}
-- source.file
dofile("ex5.lua")
-- sink.table
dofile("ex7.lua")
-- sink.chain
require"ltn12"
sink.chain = ltn12.sink.chain
-- normalize
require"gem"
eol = gem.eol
dofile("ex2.lua")
-- pump.all
dofile("ex10.lua")
-- run test
dofile("ex8.lua")

10
gem/t4.lua Normal file
View File

@ -0,0 +1,10 @@
source = {}
sink = {}
pump = {}
filter = {}
-- source.file
dofile("ex5.lua")
-- run test
dofile("ex9.lua")

30
gem/t5.lua Normal file
View File

@ -0,0 +1,30 @@
source = {}
sink = {}
pump = {}
filter = {}
-- source.chain
dofile("ex6.lua")
-- source.file
dofile("ex5.lua")
-- encode
require"mime"
encode = mime.encode
-- sink.chain
require"ltn12"
sink.chain = ltn12.sink.chain
-- wrap
wrap = mime.wrap
-- sink.file
sink.file = ltn12.sink.file
-- pump.all
dofile("ex10.lua")
-- run test
dofile("ex11.lua")

46
gem/test.lua Normal file
View File

@ -0,0 +1,46 @@
function readfile(n)
local f = io.open(n, "rb")
local s = f:read("*a")
f:close()
return s
end
lf = readfile("t1lf.txt")
os.remove("t1crlf.txt")
os.execute("lua t1.lua < t1lf.txt > t1crlf.txt")
crlf = readfile("t1crlf.txt")
assert(crlf == string.gsub(lf, "\010", "\013\010"), "broken")
gt = readfile("t2gt.qp")
os.remove("t2.qp")
os.execute("lua t2.lua < t2.txt > t2.qp")
t2 = readfile("t2.qp")
assert(gt == t2, "broken")
os.remove("t1crlf.txt")
os.execute("lua t3.lua < t1lf.txt > t1crlf.txt")
crlf = readfile("t1crlf.txt")
assert(crlf == string.gsub(lf, "\010", "\013\010"), "broken")
t = readfile("test.lua")
os.execute("lua t4.lua < test.lua > t")
t2 = readfile("t")
assert(t == t2, "broken")
os.remove("output.b64")
gt = readfile("gt.b64")
os.execute("lua t5.lua")
t5 = readfile("output.b64")
assert(gt == t5, "failed")
print("1 2 5 6 10 passed")
print("2 3 4 5 6 10 passed")
print("2 5 6 7 8 10 passed")
print("5 9 passed")
print("5 6 10 11 passed")
os.remove("t")
os.remove("t2.qp")
os.remove("t1crlf.txt")
os.remove("t11.b64")
os.remove("output.b64")