NAME
DSL::Tiny::Role - A simple yet powerful DSL builder.
VERSION
version 0.001
SYNOPSIS
# In e.g. MooseDSL.pm, describe a simple DSL.
package MooseDSL;
use Moose; # or use Moo;
with qw(DSL::Tiny::Role);
sub build_dsl_keywords {
return [
# keywords will be run through curry_method
qw(argulator return_self clear_call_log),
];
}
has call_log => (
clearer => 'clear_call_log',
default => sub { [] },
is => 'rw',
lazy => 1
);
sub argulator {
my $self = shift;
push @{ $self->call_log }, join "::", @_;
}
sub return_self { return $_[0] }
1;
################################################################
# and then in another file you can use that DSL
use Test::More;
use Test::Deep;
use MooseDSL qw( -install_dsl );
# peek under the covers, get the instance
my $dsl = return_self;
isa_ok( $dsl, 'MooseDSL' );
# test argument handling, single scalar
argulator("a scalar");
cmp_deeply( $dsl->call_log, ['a scalar'], 'scalar arg works' );
clear_call_log;
# test argument handling, list of args
argulator(qw(a list of things));
cmp_deeply( $dsl->call_log, ['a::list::of::things'], 'list arg works' );
clear_call_log;
done_testing;
DESCRIPTION
*This is an initial release. It's all subject to rethinking. Comments
welcome.*
every time a language advertises "we make writing dsls easy!" i
read "i'm going to have to learn a new language for every project"
Jesse Luehrs (@doyster) 3/8/13, 12:11 PM
Domain-specific languages (DSL's) aid in the efficient expression of
configurations, problems and solutions within a particular domain. While
some DSL's are built from the ground up with custom lexers, parsers,
etc... (e.g. the Unix build tool "make"), other "internal DSL's" (Werner
Schuster <http://www.infoq.com/news/2007/06/dsl-or-not>) are distilled
from existing languages and "speak the language of their domain with an
accent" (Piers Cawley
<http://www.bofh.org.uk/2007/05/19/domain-agnostic-languages>).
A variety of Perl tools and libraries sport domain specific langagues,
e.g. Dancer, Module-CPANfile and Module-Install and the number of
re-implementations of the underlying plumbing is almost exactly equal to
the number of such modules. These implementations usually devolve into
dirty tricks (e.g. explicit package stash manipulations) and
re-invention of several wheels.
DSL::Tiny packages the common functionality required to implement an
internal DSL, building on powerful foundations (Sub::Exporter) and
effective techniques (Moose and Moo roles) to allow developers to focus
on their domain-specific issues. It builds on a flexible mechanism for
exporting a set of subroutines into a package; provides a consistent
framework for subroutine currying; and automates the construction of
instances, their association with DSL fragments and the evaluation of
those fragments.
In other words, when I needed to build an internal DSL for a project, I
was shocked at how often the basic brushstrokes had been repeated and
how often these implementations dug down and peeked underneath Perl's
stashes. These modules are my attempt to provide a reusable solution to
the problem via existing high-leverage tools.
ATTRIBUTES
dsl_keywords
Returns an arrayref of dsl keyword info.
It is lazy. Classes which consume the role are required to supply a
builder named "_build_dsl_keywords".
In its canonical form the contents of the array reference are a series
of array references containing keyword_name => { option_hash } pairs,
e.g.
[ [ keyword1 => { as => &generator('method1') } ],
[ keyword2 => { before => &generator ]
]
Generators are as described in the Sub::Exporter documentation.
However, as the contents of this array reference are processed with
Data::OptList there is a great deal of flexibility, e.g.
[ qw( m1 m2 ), k4 => { as => &generator('some_method' } ]
is equivalent to:
[ m1 => undef, m2 => undef, k4 => { as => generator('some_method') } ]
Options are optional. In particular, if no "as" generator is provided
then the keyword name is presumed to also be the name of a method in the
class and "Sub::Exporter::Utils::curry_method" will be applied to that
method to generate the coderef for that keyword. The makes the above
equivalent to:
[ m1 => { as => generator('m1') }, m2 => { as => generator('m2') },
k4 => { as => generator('some_method') }
]
In its simplest form, the keyword arrayref contains a list of method
names relative to class which consumes this role.
[ qw( m1 m2 ) ]
Supported options include:
as
before
after
METHODS
import
An import routine generated by Sub::Exporter.
When invoked as a class method (usually via "use") with a "-install_dsl"
argument it will construct a new instance then generate and install a
set of subroutines using the information provided in the instance's
"dsl_keywords" attribute.
TODO.
install_dsl
A synonym for the Sub::Exporter generated import method. Sounds better
when one uses it to install into an instance.
_dsl_build
Private-ish. Do you really want to be here?
"_dsl_build" build's up the set of keywords that Sub::Exporter will
install.
It returns a hashref whose keys are names of keywords and whose values
are coderefs implementing the respective behavior.
It can be invoked on a class (a.k.a. as a class method), usually by
"use". If so, a new instance of the class will be constructed and the
various keywords are curried with respect to that instance.
It can be invoked on a class instance, e.g. via an explicit invocation
of import on an instance. If so, then that instance is used when
constructing the keywords.
_compile_keyword
Private, go away.
Generate a sub that implements the keyword, taking care of before's and
afters.
REQUIRES
build_dsl_keywords
A subroutine, used as the Moo{,se} builder for the "dsl_keywords"
attribute. It returns an array reference containing information about
the methods and subroutines that implement the keywords in the DSL.
AUTHOR
George Hartzell <hartzell@alerce.com>
COPYRIGHT AND LICENSE
This software is copyright (c) 2013 by George Hartzell.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.