Micah Stetson
2010-02-17 19:02:32 UTC
I have mostly implemented an idea in the PHP version of json-template
that I think is generally useful. I am using it for i18n, but it's
more widely applicable than that. "On Design Minimalism" suggests
that json-template is a functional programming language. If that's
so, then what I'm proposing is analogous to Lisp's macros. Here's
what I'm doing:
Parsing a json-template program produces a JSON (or language
equivalent) value as follows:
A program is a statement or an array of statements.
A formatter list is an array of strings.
A statement is either a string or an object in one of the following
forms:
{"type":"section", "name":<string>, "contents":<program>,
"or":<program>}
{"type":"repeated section", "name":<string>,
"contents":<program>, "or": <program>, "alternates_with":<program>}
{"type":"substitution", "name":<string>, "formatters":<formatter
list>}
{"type":"trans", "formatters":<formatter list>, "contents":<program>}
Type trans, short for transformation, is for a new block type:
{.trans(|formatter)*}...{.end}, which I'll talk more about in a
moment. So this template:
Members present: {.repeated section members}{@|html}{.alternates
with}, {.or}None{.end}
Would be parsed into this value:
["Members present: ",
{"type":"repeated section","name":"members",
"contents":[{"type":"substitution","name":"@","formatters":["html"]}]
"or":["None"],
"alternates_with":[", "]}]
Evaluation of these structures is done as you might expect: an array
of statements evaluates to the concatenation of the evaluation of each
element; a string evaluates to itself; section, repeated section, and
substitution evaluate according to the current rules. Trans, however,
passes the parsed representation of its contents to a formatter
pipeline, and then evaluates the result. That's the key: evaluation
after processing.
In addition, the json-template module makes a few new functions
public: one which evaluates a program in a given scoped context, one
that parses json-template source code into a program, and one that
converts a program into equivalent json-template source code. An
additional formatter is installed by default named 'eval' that
evaluates its input as a program in the current scoped context -- by
the definitions above, this formatter is a no-op on strings. Also,
the parser accepts a new option, default-trans, specifying the
formatter to use in transformations, when none is specified.
This mechanism isn't complicated to implement, and it enables a lot of
new function. First, translation: write a formatter that converts its
program input into json-template source code, uses that source code as
a key for lookup in a translation table, then parses and returns the
translated value as the program to be evaluated. Set this as the
default transform, and {.trans}My name is {name}{.end} will translate
the string before evaluating the substitutions. Since formatters can
take arguments, you could make a variant of the translate formatter
called plural that looks up a number in context and translates a
string using the appropriate plural form for that value: {.trans|
plural count}There are {count} items.{.end} would be translated to
"There is one item." if count==1.
Using eval, simple formatters can be used as transforms as well:
{.trans|eval|html}a<b && c>d{.end} would become "a<b &&
c>d".
A formatter that implements a simple expression language: {.trans|eval|
expr}{quantity}*{cost}{.end}
True inclusion (for good or evil): {.trans|include}path/to/
template.jsont{.end}
If you like the idea, I can polish up my code and submit it for
review.
Micah
that I think is generally useful. I am using it for i18n, but it's
more widely applicable than that. "On Design Minimalism" suggests
that json-template is a functional programming language. If that's
so, then what I'm proposing is analogous to Lisp's macros. Here's
what I'm doing:
Parsing a json-template program produces a JSON (or language
equivalent) value as follows:
A program is a statement or an array of statements.
A formatter list is an array of strings.
A statement is either a string or an object in one of the following
forms:
{"type":"section", "name":<string>, "contents":<program>,
"or":<program>}
{"type":"repeated section", "name":<string>,
"contents":<program>, "or": <program>, "alternates_with":<program>}
{"type":"substitution", "name":<string>, "formatters":<formatter
list>}
{"type":"trans", "formatters":<formatter list>, "contents":<program>}
Type trans, short for transformation, is for a new block type:
{.trans(|formatter)*}...{.end}, which I'll talk more about in a
moment. So this template:
Members present: {.repeated section members}{@|html}{.alternates
with}, {.or}None{.end}
Would be parsed into this value:
["Members present: ",
{"type":"repeated section","name":"members",
"contents":[{"type":"substitution","name":"@","formatters":["html"]}]
"or":["None"],
"alternates_with":[", "]}]
Evaluation of these structures is done as you might expect: an array
of statements evaluates to the concatenation of the evaluation of each
element; a string evaluates to itself; section, repeated section, and
substitution evaluate according to the current rules. Trans, however,
passes the parsed representation of its contents to a formatter
pipeline, and then evaluates the result. That's the key: evaluation
after processing.
In addition, the json-template module makes a few new functions
public: one which evaluates a program in a given scoped context, one
that parses json-template source code into a program, and one that
converts a program into equivalent json-template source code. An
additional formatter is installed by default named 'eval' that
evaluates its input as a program in the current scoped context -- by
the definitions above, this formatter is a no-op on strings. Also,
the parser accepts a new option, default-trans, specifying the
formatter to use in transformations, when none is specified.
This mechanism isn't complicated to implement, and it enables a lot of
new function. First, translation: write a formatter that converts its
program input into json-template source code, uses that source code as
a key for lookup in a translation table, then parses and returns the
translated value as the program to be evaluated. Set this as the
default transform, and {.trans}My name is {name}{.end} will translate
the string before evaluating the substitutions. Since formatters can
take arguments, you could make a variant of the translate formatter
called plural that looks up a number in context and translates a
string using the appropriate plural form for that value: {.trans|
plural count}There are {count} items.{.end} would be translated to
"There is one item." if count==1.
Using eval, simple formatters can be used as transforms as well:
{.trans|eval|html}a<b && c>d{.end} would become "a<b &&
c>d".
A formatter that implements a simple expression language: {.trans|eval|
expr}{quantity}*{cost}{.end}
True inclusion (for good or evil): {.trans|include}path/to/
template.jsont{.end}
If you like the idea, I can polish up my code and submit it for
review.
Micah
--
You received this message because you are subscribed to the Google Groups "JSON Template" group.
To post to this group, send email to json-template-/JYPxA39Uh5TLH3MbocFF+G/***@public.gmane.org
To unsubscribe from this group, send email to json-template+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/***@public.gmane.org
For more options, visit this group at http://groups.google.com/group/json-template?hl=en.
You received this message because you are subscribed to the Google Groups "JSON Template" group.
To post to this group, send email to json-template-/JYPxA39Uh5TLH3MbocFF+G/***@public.gmane.org
To unsubscribe from this group, send email to json-template+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/***@public.gmane.org
For more options, visit this group at http://groups.google.com/group/json-template?hl=en.