DSL::Tiny::Role - A simple yet powerful DSL builder.
version 0.001
# 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;
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) are distilled from existing languages and "speak the language of their domain with an accent" (Piers Cawley).
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.
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
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.
A synonym for the Sub::Exporter generated import method. Sounds better when one uses it to install into an instance.
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.
Private, go away.
Generate a sub that implements the keyword, taking care of before's and afters.
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.
George Hartzell <hartzell@alerce.com>
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.