NAME
Decl - Provides a declarative framework for Perl
VERSION
Version 0.11
SYNOPSIS
This module is a framework for writing Perl code in a declarative manner. What that means right now is that instead of seeing a script as a series of actions to be carried out, you can view the script as a set of objects to be instantiated, then invoked. The syntax for building these objects is intended to be concise and flexible, mostly staying out of your way. Perl code is used to declare actions to be taken once the structure is built, as well as any actions to be taken interactively as the script runs.
The original motivation for designing this framework was to provide a more rational way of defining a Wx user interface. As it is, the data structures making up a Wx GUI are built with painstakingly detailed (and boring) imperative code. There are XML-based GUI specification frameworks, but I wanted to write my own that wasn't XML-based because I hate typing XML even more than I hate writing setup code.
Back when I did a lot of GUI work, I'd usually write some pseudocode to describe parts of the UI, then translate it into code by hand. So this year, while noodling around about some tools I'd find useful in my translation business, I thought, well, why not just write a class to interpret that pseudocode description directly?
Once I started getting into that in earnest, I realized that the Wx-specific functionality could be spun out into an application-specific (in my new parlance, a "semantic") domain, leaving a core set of functionality that was a general declarative framework. I then realized that the same framework could easily be used to work with domains other than Wx GUIs, such as building PDFs, building Flash applications, doing things with Word documents... All kinds of things. All of those things are currently in pieces on the workbench - except for the Word module, which is ready, if not for prime time, then at least for deep cable midnight airing.
Here's a GUI example using something like the Wx domain. This is a pretty simple example, but it gives you a taste of what I'm talking about. Since Decl runs as a source filter, the example below is a working Perl script that replaces roughly 80 lines of the Wx example code it was adapted from. And yes, it runs in my test suite right now.
use Wx::Declarative;
dialog (xsize=250, ysize=110) "Wx::Declarative dialog sample"
field celsius (size=100, x=20, y=20) "0"
button celsius (x=130, y=20) "Celsius" { $^fahrenheit = ($^celsius / 100.0) * 180 + 32; }
field fahrenheit (size=100, x=20, y=50) "32"
button fahrenheit (x=130, y=50) "Fahrenheit" { $^celsius = (($^fahrenheit - 32) / 180.0) * 100; }
The main things to look at are as follows: first, yes - syntactically significant indentation. I know it's suspiciously Pythonic, I know all the arguments citing the danger of getting things to line up, and I don't care; this is the way I have always written my pseudocode, and odds are you're no different and you know it. If it makes you feel better, the indentation detection algorithm is pretty flexible, and Perl code within curly braces is exempt from indentation significance. (Not that this example has any multiline code, but you see what I mean.)
Second, fields are declared here and their content is exposed as magic variables in the code snippets. You will immediately see that code embedded in a declarative structure goes through a modification pass before being eval
'd into a sub. So there is a possibility that I have screwed that modification pass up. I don't have an answer for this right now; the point is quick and easy, not perfection (yet). Caveat emptor. It's still a neat feature.
There is a standard parser and standard data structure available for tags to use if it suits your purpose - but there's no mandate to use them, and the parser tools are open for use. They're still a little raw, but pretty powerful.
A declarative object can report its own source code, and that source code can compile into an equivalent declarative object. This means that dynamically constructed objects or applications can be written out as executable code, and code has introspective capability while in the loaded state. Decl
also has a macro system that allows the construction of code during the build phase; a macro always dumps as its source, not the result of the expansion, so you can capture dynamic behavior that runs dynamically every time.
TUTORIAL
For more information about how to use Decl
, you'll probably want to see the tutorial in Decl::Semantics instead of this file; the rest of this presentation is devoted to the internal workings of Decl
. (Old literate programming habits, I guess.) Honestly, you can probably just stop here, because if you're not reading the source along with the POD it probably won't make any sense anyway. Go read the tutorial. Not that I've finished it.
SETTING UP THE CLASS STRUCTURE
import, yes_i_am_declarative, import_one
The import
function is called when the package is imported. It's used for the filter support; don't call it.
If semantic classes are supplied in the use
command, we're going to instantiate and scan them here. They'll be used to decorate the parse tree appropriately.
class_builders(), find_tagdef($parent, $tag), build_handler ($parent, $tag), register_builder ($node)
Given a tag name, class_build_handler
returns a hashref of information about how the tag expects to be treated:
* The class its objects should be blessed into, as a coderef to generate the object ('Decl::Node' is the default) * Its line parser, by name ('default-line' is the default) * Its body parser, by name ('default-body' is the default) * A second-level hashref of hashrefs providing overriding semantics for descendants of this tag.
If you also provide a hashref, it is assigned to the tag name.
The app_build_handler
does the same thing, but specific to the given application - this allows dynamic tag definition.
Finally, build_handler
is a read-only lookup for a tag in the context of its ancestry that climbs the tree to find the contextual semantics for the tag.
makenode($ancestry, $code)
Finds the right build handler for the tag in question, then builds the right class of node with the code given.
remakenode($node)
If it turns out that things have changed semantically since we split a node out, and the node hasn't been built yet (this is specifically to support the "use" tag), then we can signal that the node should be remade, and we'll build and substitute a new node based on the new semantic environment and using the information available to us in the initially created node.
FILTERING SOURCE CODE
By default, Decl
runs as a filter. That means it intercepts code coming in and can change it before Perl starts parsing. Needless to say, filters act very cautiously, because the only thing that can parse Perl correctly is Perl (and sometimes even Perl has doubts). So this filter basically just wraps the entire input source in a call to new
, which is then parsed and called after the filter returns.
filter
The filter
function is called by the source code filtering process. You probably don't want to call it. But if you've ever wondered how difficult it is to write a source code filter, read it. Hint: it really isn't difficult.
PARSERS
The parsing process in Decl
is recursive. The basic form is a tagged line followed by indented text, followed by another tagged line with indented text, and so on. Alternatively, the indented part can be surrounded by brackets.
tag [rest of line]
indented text
indented text
indented text
tag [rest of line] {
bracketed text
bracketed text
}
By default, each tag parses its indented text in the same way, and it's turtles all the way down. Bracketed text, however, is normally not parsed as declarative (or "nodal") structure, but is left untouched for special handling, typically being parsed by Perl and wrapped as a closure.
To force content to be handled as text instead of nodal structure, put a period on the end of the tag. Some tags are defined with this as the default; for these you can force normal nodal structure with a '!', or data-only nodal structure with a '*'.
However, all this is merely the default. Any tag may also specify a different parser for its own indented text, or may carry out some transformation on the text before invoking the parser. It's up to the tag. The data
tag, for instance, treats each indented line as a row in a table.
Once the body is handled, the "rest of line" is also parsed into data useful for the node. Again, there is a default parser, which takes a line of the following form:
tag name (parameter, parameter=value) [option, option=value] "label or other string text" parser < { bracketed text }
Any element of that line may be omitted, except for the tag.
init_parsers()
Sets up the registry and builds our default line and body parsers.
parser($name)
Retrieves a parser from the registry.
parse_line ($node)
Given a node, finds the line parser for it, and runs it on the node's line.
parse($node, $body)
Given a node and body text for it, finds the body parser appropriate to the node's tag and runs it on the node and the body text specified.
parse_using($string, $parser)
Given a string and the name of a parser, calls the parser on the string and returns the result.
TEMPLATE ENGINE
The macro system in Decl uses a template engine implemented in Decl::Template. However, the plain vanilla "valuator" (the function used by a given template engine instance to find values for fields with particular names/specs) is replaced in the Decl node environment by a much more powerful valuator. That valuator is implemented in Decl::NodalValuator.
We instantiate a template engine with a nodal valuator for use by the macro system here.
BUILDING AND MANAGING THE APPLICATION
You'd think this would be up at the top, but we had to do a lot of work just to be ready to instantiate a Decl
object.
new, new_data, new_data_with_label
The new
function is of course called to create a new Decl
object. If you pass it some code, it will load that code immediately.
The new_data
is used if you don't want anything to have any semantics or action. It's used for some internal data structures. "Describe" works the same way, not specifying the root tag. This may not be what you want.
Finally new_data_with_label
allows you to provide a different *-tag for the data; this could be useful for debugging. Or I might get rid of it. I don't know yet. It's only used internally in this module anyway.
initiate_semantic_class
Does what it says on the tin.
semantic_handler ($tag)
Returns the instance of a semantic module, such as 'core' or 'wx'.
start
This is called from outside to kick off the process defined in this application. The way we handle this is just to ask the first semantic class to start itself. The idea there being that it's probably going to be Wx or something that provides the interface. (It could also be a Web server or something.)
The core semantics just execute all the top-level items that are flagged callable.
id($idstring)
Wx works with numeric IDs for events, and I presume the other event-based systems do, too. I don't like numbers; they're hard to read and tell apart. So Decl
registers event names for you, assigning application-wide unique numeric IDs you can use in your payload objects.
root()
Returns $self; for nodes, returns the parent. The upshot is that by calling root
we can get the root of the tree, fast.
mylocation()
Special case: returns a slash. (It's the root.)
describe([$use])
Returns a reconstructed set of source code used to compile this present Decl
object. If it was assembled in parts, you still get the whole thing back. Macro results are not included in this dump (they're presumed to be the result of macros in the tree itself, so they should be regenerated the next time anyway).
If you specify a true value for $use, the dump will include a "use" statement at the start in order to make the result an executable Perl script. The dump is always in filter format (if you built it with -nofilter) and contains Decl
's best guess of the semantic modules used. If you're using a "use lib" to affect your %INC, the result won't work right unless you modify it, but if it's all standard modules, the dump result, after loading, should work the same as the original entry.
find_data
The find_data
function finds a top-level data node.
write, log
Normal nodes send these to their parents if not otherwise set for the node; at the top level, unless otherwise set, we print to STDOUT or STDERR.
FILTER REGISTRY
A filter
in Decl is just a function that takes one string and returns another. (TODO: something iterator- and stream-aware, I suppose.) It's used for text blocks. A filter call can take additional parameters as well, but doesn't have to.
Filters are called using call_filter
on any given node; a search is made for the appropriate filter and it's invoked, if found. If it's not found, then a globally registered filter is called (this permits libraries to contain filters). This filter registry is where that is managed.
register_filter ($name, $coderef, $origin)
During load, a module can register a filter with register_filter
. (It can happen any other time, too, of course.) To find a registered filter, you can call register_filter without a code reference, and if there is such a filter registered under the name, it will be returned.
The $origin
parameter is something you can use for debugging.
Decl->register_filter('myfilter', sub { ... }, 'where I defined this');
registered_filters()
Returns a sorted list of all global filter names.
AUTHOR
Michael Roberts, <michael at vivtek.com>
BUGS
Please report any bugs or feature requests to bug-decl at rt.cpan.org
, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Decl. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Decl
You can also look for information at:
RT: CPAN's request tracker
AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
Search CPAN
ACKNOWLEDGEMENTS
LICENSE AND COPYRIGHT
Copyright 2011 Michael Roberts.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.