Return-Path: X-Original-To: notmuch@notmuchmail.org Delivered-To: notmuch@notmuchmail.org Received: from localhost (localhost [127.0.0.1]) by olra.theworths.org (Postfix) with ESMTP id 1582D431FB6 for ; Thu, 6 Dec 2012 07:44:10 -0800 (PST) X-Virus-Scanned: Debian amavisd-new at olra.theworths.org X-Spam-Flag: NO X-Spam-Score: -0.7 X-Spam-Level: X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5 tests=[RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled Received: from olra.theworths.org ([127.0.0.1]) by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id xNSGtffUoBDR for ; Thu, 6 Dec 2012 07:44:09 -0800 (PST) Received: from dmz-mailsec-scanner-8.mit.edu (DMZ-MAILSEC-SCANNER-8.MIT.EDU [18.7.68.37]) by olra.theworths.org (Postfix) with ESMTP id BB48B431FAE for ; Thu, 6 Dec 2012 07:44:08 -0800 (PST) X-AuditID: 12074425-b7f606d0000008ea-06-50c0bd48dfca Received: from mailhub-auth-2.mit.edu ( [18.7.62.36]) by dmz-mailsec-scanner-8.mit.edu (Symantec Messaging Gateway) with SMTP id A8.D6.02282.84DB0C05; Thu, 6 Dec 2012 10:44:08 -0500 (EST) Received: from outgoing.mit.edu (OUTGOING-AUTH.MIT.EDU [18.7.22.103]) by mailhub-auth-2.mit.edu (8.13.8/8.9.2) with ESMTP id qB6Fi7WX015398; Thu, 6 Dec 2012 10:44:08 -0500 Received: from awakening.csail.mit.edu (awakening.csail.mit.edu [18.26.4.91]) (authenticated bits=0) (User authenticated as amdragon@ATHENA.MIT.EDU) by outgoing.mit.edu (8.13.6/8.12.4) with ESMTP id qB6Fi5KG023835 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES128-SHA bits=128 verify=NOT); Thu, 6 Dec 2012 10:44:06 -0500 (EST) Received: from amthrax by awakening.csail.mit.edu with local (Exim 4.80) (envelope-from ) id 1TgdcL-0005zu-4p; Thu, 06 Dec 2012 10:44:05 -0500 From: Austin Clements To: Peter Feigl , notmuch@notmuchmail.org Subject: Re: [PATCH v4 1/5] Adding an S-expression structured output printer. In-Reply-To: <1409adf6cbd310d7313bdc3a69e7e78c25859d81.1354794428.git.craven@gmx.net> References: <1409adf6cbd310d7313bdc3a69e7e78c25859d81.1354794428.git.craven@gmx.net> User-Agent: Notmuch/0.14+159~g6895fee (http://notmuchmail.org) Emacs/23.4.1 (i486-pc-linux-gnu) Date: Thu, 06 Dec 2012 10:44:05 -0500 Message-ID: <87hanzp4my.fsf@awakening.csail.mit.edu> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFmphleLIzCtJLcpLzFFi42IRYrdT0fXYeyDA4NF5Lou9De2MFtdvzmR2 YPJYvGk/m8ezVbeYA5iiuGxSUnMyy1KL9O0SuDLaXv5mKzjhWfHhmlkD4y3zLkZODgkBE4kZ RzeyQNhiEhfurWfrYuTiEBLYxyjx5fNuRghnPaPE7+4OdgjnApPEwm2NUM4SRokVD7vB+tkE 9CVWrJ3ECmKLCFhKTP1yiQ3EFhbwlTiz5Q4ziM0pECqxtHk6I4gtJFAlcf/bUaAaDg5RgXiJ 2ed8QMIsAqoSD679ZAKxeYHOa959GcoWlDg58wnYKmYBdYk/8y4xQ9jaEssWvmaewCg4C0nZ LCRls5CULWBkXsUom5JbpZubmJlTnJqsW5ycmJeXWqRroZebWaKXmlK6iREcwC6qOxgnHFI6 xCjAwajEw2tRvT9AiDWxrLgy9xCjJAeTkihv9vYDAUJ8SfkplRmJxRnxRaU5qcWHGCU4mJVE eGM6gHK8KYmVValF+TApaQ4WJXHeGyk3/YUE0hNLUrNTUwtSi2CyMhwcShK8m3cDNQoWpaan VqRl5pQgpJk4OEGG8wANjwap4S0uSMwtzkyHyJ9i1OVYuKb9CaMQS15+XqqUOO9ZkCIBkKKM 0jy4ObDE84pRHOgtYd75IFU8wKQFN+kV0BImoCVR7PtBlpQkIqSkGhj3KU77EnXJwqKm+JdU ol/1L8+7DHsDb4vlPG8qS/3JE+Aup3d9Q3/hp6sy37gETZYm2lxIycp3vDr/QWF4wiFRjY/r ZgqyrD/l+UFv2sfKW4IWxwq918/jMI95yZI0/b3eVLF426PHzhzzfLPjy5qa433Rf4+U7ZG1 /+HNcPzGjzl2/40Mz/QosRRnJBpqMRcVJwIAOJZnlBcDAAA= X-BeenThere: notmuch@notmuchmail.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 06 Dec 2012 15:44:10 -0000 On Thu, 06 Dec 2012, Peter Feigl wrote: > This commit adds a structured output printer for Lisp > S-Expressions. Later commits will use this printer in notmuch search, > show and reply. > > The structure is the same as json, but: > - arrays are written as lists: ("foo" "bar" "baaz" 1 2 3) > - maps are written as p-lists: (:key "value" :other-key "other-value") > - true is written as t > - false is written as nil > - null is written as nil > --- > Makefile.local | 1 + > sprinter-sexp.c | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++= ++++++ > sprinter.h | 4 + > 3 files changed, 243 insertions(+) > create mode 100644 sprinter-sexp.c > > diff --git a/Makefile.local b/Makefile.local > index 2b91946..0db1713 100644 > --- a/Makefile.local > +++ b/Makefile.local > @@ -270,6 +270,7 @@ notmuch_client_srcs =3D \ > notmuch-tag.c \ > notmuch-time.c \ > sprinter-json.c \ > + sprinter-sexp.c \ > sprinter-text.c \ > query-string.c \ > mime-node.c \ > diff --git a/sprinter-sexp.c b/sprinter-sexp.c > new file mode 100644 > index 0000000..8f84eed > --- /dev/null > +++ b/sprinter-sexp.c > @@ -0,0 +1,238 @@ > +/* notmuch - Not much of an email program, (just index and search) > + * > + * Copyright =C2=A9 2012 Peter Feigl > + * > + * This program is free software: you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation, either version 3 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program. If not, see http://www.gnu.org/licenses/ . > + * > + * Author: Peter Feigl > + */ > + > +#include > +#include > +#include > +#include "sprinter.h" > + > +struct sprinter_sexp { > + struct sprinter vtable; > + FILE *stream; > + /* Top of the state stack, or NULL if the printer is not currently > + * inside any aggregate types. */ > + struct sexp_state *state; > + > + /* A flag to signify that a separator should be inserted in the > + * output as soon as possible. */ > + notmuch_bool_t insert_separator; > +}; > + > +struct sexp_state { > + struct sexp_state *parent; > + > + /* True if nothing has been printed in this aggregate yet. > + * Suppresses the space before a value. */ > + notmuch_bool_t first; > +}; > + > +/* Helper function to set up the stream to print a value. If this > + * value follows another value, prints a space. */ > +static struct sprinter_sexp * > +sexp_begin_value (struct sprinter *sp) > +{ > + struct sprinter_sexp *sps =3D (struct sprinter_sexp *) sp; > + > + if (sps->state) { > + if (! sps->state->first) { > + if (sps->insert_separator) { > + fputc ('\n', sps->stream); > + sps->insert_separator =3D FALSE; > + } else { > + fputc (' ', sps->stream); > + } > + } else { > + sps->state->first =3D FALSE; > + } > + } > + return sps; > +} > + > +/* Helper function to begin an aggregate type. Prints the open > + * character and pushes a new state frame. */ > +static void > +sexp_begin_aggregate (struct sprinter *sp) > +{ > + struct sprinter_sexp *sps =3D sexp_begin_value (sp); > + struct sexp_state *state =3D talloc (sps, struct sexp_state); > + fputc ('(', sps->stream); > + state->parent =3D sps->state; > + state->first =3D TRUE; > + sps->state =3D state; > +} > + > +static void > +sexp_begin_map (struct sprinter *sp) > +{ > + sexp_begin_aggregate (sp); > +} > + > +static void > +sexp_begin_list (struct sprinter *sp) > +{ > + sexp_begin_aggregate (sp); > +} > + > +static void > +sexp_end (struct sprinter *sp) > +{ > + struct sprinter_sexp *sps =3D (struct sprinter_sexp *) sp; > + struct sexp_state *state =3D sps->state; > + > + fputc (')', sps->stream); > + sps->state =3D state->parent; > + talloc_free (state); > + if (sps->state =3D=3D NULL) > + fputc ('\n', sps->stream); > +} > + > +static void > +sexp_string_len (struct sprinter *sp, const char *val, size_t len) > +{ > + /* Some characters need escaping. " and \ work fine in all Lisps, > + * \n is not supported in CL, but all others work fine. > + * Characters below 32 are printed as \123o (three-digit=20 > + * octals), which work fine in most Schemes and Emacs. */ > + static const char *const escapes[] =3D { > + ['\"'] =3D "\\\"", ['\\'] =3D "\\\\", ['\n'] =3D "\\n" > + }; > + struct sprinter_sexp *sps =3D sexp_begin_value (sp); > + > + fputc ('"', sps->stream); > + for (; len; ++val, --len) { > + unsigned char ch =3D *val; > + if (ch < ARRAY_SIZE (escapes) && escapes[ch]) > + fputs (escapes[ch], sps->stream); > + else if (ch >=3D 32) > + fputc (ch, sps->stream); > + else > + fprintf (sps->stream, "\\%03oo", ch); Should be "\\%03o" (one less o). > + } > + fputc ('"', sps->stream); > +} > + > +static void > +sexp_string (struct sprinter *sp, const char *val) > +{ > + if (val =3D=3D NULL) > + val =3D ""; > + sexp_string_len (sp, val, strlen (val)); > +} > + > +/* Prints a symbol, i.e. the name preceded by a colon. This should work > + * in all Lisps, at least as a symbol, if not as a proper keyword */ > +static void > +sexp_symbol (struct sprinter *sp, const char *val) > +{ > + static const char illegal_characters[] =3D { > + ' ', '\t', '\n' There are a large number of illegal symbol characters. I would go the conservative route here (since we get to choose our symbols) and only allow isalnum(x) || (x =3D=3D '-') || (x =3D=3D '_') (I would prefer not to allow _ but it's already in the schema.) > + }; > + unsigned int i =3D 0; > + struct sprinter_sexp *sps =3D (struct sprinter_sexp *) sp; > + > + if (val =3D=3D NULL) > + INTERNAL_ERROR ("illegal symbol NULL"); > + > + for(i =3D 0; i < ARRAY_SIZE (illegal_characters); i++) { > + if(strchr(val, illegal_characters[i]) !=3D NULL) { > + INTERNAL_ERROR ("illegal character in symbol %s", val); > + } > + } > + fputc (':', sps->stream); > + fputs (val, sps->stream); > +} > + > +static void > +sexp_integer (struct sprinter *sp, int val) > +{ > + struct sprinter_sexp *sps =3D sexp_begin_value (sp); > + > + fprintf (sps->stream, "%d", val); > +} > + > +static void > +sexp_boolean (struct sprinter *sp, notmuch_bool_t val) > +{ > + struct sprinter_sexp *sps =3D sexp_begin_value (sp); > + > + fputs (val ? "t" : "nil", sps->stream); > +} > + > +static void > +sexp_null (struct sprinter *sp) > +{ > + struct sprinter_sexp *sps =3D sexp_begin_value (sp); > + > + fputs ("nil", sps->stream); > +} > + > +static void > +sexp_map_key (struct sprinter *sp, const char *key) > +{ > + struct sprinter_sexp *sps =3D (struct sprinter_sexp *) sp; > + if (sps->state && ! sps->state->first) > + fputc (' ', sps->stream); > + > + sps->state->first =3D FALSE; > + sexp_symbol (sp, key); > +} How about struct sprinter_sexp *sps =3D sexp_begin_value (sp); sexp_symbol (sp, key); ? > + > +static void > +sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name)) > +{ > +} > + > +static void > +sexp_separator (struct sprinter *sp) > +{ > + struct sprinter_sexp *sps =3D (struct sprinter_sexp *) sp; > + > + sps->insert_separator =3D TRUE; > +} > + > +struct sprinter * > +sprinter_sexp_create (const void *ctx, FILE *stream) > +{ > + static const struct sprinter_sexp template =3D { > + .vtable =3D { > + .begin_map =3D sexp_begin_map, > + .begin_list =3D sexp_begin_list, > + .end =3D sexp_end, > + .string =3D sexp_string, > + .string_len =3D sexp_string_len, > + .integer =3D sexp_integer, > + .boolean =3D sexp_boolean, > + .null =3D sexp_null, > + .map_key =3D sexp_map_key, > + .separator =3D sexp_separator, > + .set_prefix =3D sexp_set_prefix, > + .is_text_printer =3D FALSE, > + } > + }; > + struct sprinter_sexp *res; > + > + res =3D talloc (ctx, struct sprinter_sexp); > + if (! res) > + return NULL; > + > + *res =3D template; > + res->stream =3D stream; > + return &res->vtable; > +} > diff --git a/sprinter.h b/sprinter.h > index 912a526..59776a9 100644 > --- a/sprinter.h > +++ b/sprinter.h > @@ -70,4 +70,8 @@ sprinter_text_create (const void *ctx, FILE *stream); > struct sprinter * > sprinter_json_create (const void *ctx, FILE *stream); >=20=20 > +/* Create a new structure printer that emits S-Expressions. */ > +struct sprinter * > +sprinter_sexp_create (const void *ctx, FILE *stream); > + > #endif // NOTMUCH_SPRINTER_H > --=20 > 1.8.0 > > _______________________________________________ > notmuch mailing list > notmuch@notmuchmail.org > http://notmuchmail.org/mailman/listinfo/notmuch