in reply to Re^2: Which internal DSL are there in Perl? (Domain Specific Languages - Part 1)
in thread Which internal DSL are there in Perl? (Domain Specific Languages - Part 1)

Absolutely, but why didn't you publish?

Because, well... tests, pod, you name it... then, mainly because HTML::Writer is more a proof of concept than of productive use: because it is quite slow and because of inherent namespace pollution - all those functions derived from a DTD, yecch! security... I foresaw a lot of issues with that, and since chromatic pointed me to Technical Debt, I didn't dare to. Had somebody expressed interest to work that out with me into a module proper, with defined use cases and what not, making it more generic and so on... but no. At that time I had to move on, and other things to do. It rests in the vault of unfinished projects.

<update>
If you (as in "anybody") want to grab that code and make it into a CPAN module, feel free to do so. But please include references to tmoertel and me. And I would appreciate if you contact me (via PM here or my CPAN mail handle shmem-at-cpan.org) to discuss things. Thanks.
</update>

<update2>
The module is now on github and will eventually make its way to CPAN.
</update2>

FWIW I worked on a similar project, but took some other design decisions. (Attributes are not proper functions)

Publish or show, or it didn't happen ;-)

perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
  • Comment on Re^3: Which internal DSL are there in Perl? (Domain Specific Languages - Part 1)

Replies are listed 'Best First'.
Re^4: Which internal DSL are there in Perl? (Domain Specific Languages - Part 1)
by LanX (Saint) on Aug 03, 2017 at 18:51 UTC
    > because it is quite slow and because of inherent namespace pollution

    you mention two very important problems I wanted to name in my talk

    • performance: One needs to reduce runtime as much as possible, like by memoization
    • namespace polution: The effects of the embedded language must be contained to a lexical scope.
    You are already using one basic tool to achieve this: blocks.

    performance

    All your html/xml elements take a code-block as *{$_} = sub(&) { _elem($name, @_) }; and should per default memorize the result and return the pre-calculated result.

    Of course one would need exception for cases where dynamic elements have to be interpolated, like in loops, either by explicitly deactivating memoization or by parsing the block for closed over variables.

    namespace

    1. The most obvious solution is to use a package , something like

    render { package HTML::DSL; HTML { HEAD { TITLE { "foo bar"}; }; ... }

    2. Of course it would be syntactically "sweeter" if the package was automatically included.

    The way to go is to use B::Deparse 's coderef2text to get the source and to re-eval it with an included package declaration in the first line.

    Actually it's a bit more complicated, because you might have closed over variables inside the block which would loose their binding after evaluation.

    That's why you need to inspect to take care for this, I already wrote code for this in my Macro module (github only).

    This is actually the way how many (most) DSL's are constructed in Ruby, they "re-eval" the code (instance_eval or instance_exec) from inside a block.

    And since they ran into the same problem with lost bindings of closure vars, a module called Docile was created to reestablish the bindings ...

    ... or in their own words "the hard part", where most folks making a DSL in Ruby throw up their hands

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

      All your html/xml elements take a code-block as *{$_} = sub(&) { _elem($name, @_) }; and should per default memorize the result and return the pre-calculated result.

      How would you do that? In the anonsub, I can inspect @_, but how would you determine what _elem() returns, without that subroutine being called? I can't think of a way to tell whether the codepath is entirely static.

      But memoizing should be done per DTD inside HTML::Writer, so import() doesn't do the DTD parsing again and again.

      The most obvious solution is to use a package , something like ...

      TIMTOWTDI. The prescripted usage could be

      package Test; my $foo = "my \$foo"; our $bar = "our \$bar"; my $pack = __PACKAGE__; sub baz { "$_[0] called Test::baz" } package Page { use HTML::Writer qw(xhtml1-transitional.dtd); push local @ISA, $pack; render { HTML { HEAD { TITLE { "foo bar"}; }; BODY { class_ "ugly"; onload_ "javascript: mumble()"; DIV { class_ "foo"; id_ "bar"; t "If in doubt, mumble."; IMG { src_ "foo.jpg" }; }; TABLE { my $c; for ($foo, $bar, Page->baz) { TR { TD { $_ }; TD { $c++}; } } }; DIV { class_ "bar"; t "End of that." }; } }; }; };

      which constrains the DSL symbols to the Page package, whilst having the outer context visible.

      Other ways are possible, of course. More thinking required.

      The re-evaling would introduce yet more overhead, see 1) performance...

      update: in the above example, in package Test there is my $pack = __PACKAGE__;. Is there a way to get at the name of the lexically surrounding package inside package Page ?

      perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
        Is there a way to get at the name of the lexically surrounding package inside package Page ?
        To further beech's hint in the CB: perlmod says
        The scope of the package declaration is … until the next package declaration.
        which basically means packages can't surround each other, your packages Test and Page are on the same level.
        I'm not sure why you are trying to load subs from the outer package as methods but you could basically import to the target DSL package without exporting to the calling package.

        You basically can export the whole DSL logic and anything else to "Page" and reuse the package multiple times.

        package Pack; use strict; use warnings; sub bla { warn ((caller(0))[3]); } use Import2Package 'DTD' => 'Page'; package Page { Page->bla(); }

        exec "perl import2package.pl" unless caller(); use strict; use warnings; package Import2Package; sub import { my ($self, $dtd, $pack) =@_; my $super = (caller())[0]; no strict 'refs'; push @{"${pack}::ISA"}, $super; } 1;

        Pack::bla at import2package.pl line 7.

        update

        of course TIMTOWTDI.

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!

        Another way is using a (very safe) Source Filter, which is just introducing a package XYZ; line and memorizing the original package and switching back in an unimport.

        use My::DSL; ... DSL logic ... no My::DSL;

        keep in mind that there is still this %^H hint-hash mechanism for lexically scoped exports.

        See perlpragma , though the routines are not really unimported they are just aware about the correct scope.

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!

        why not

        my $baz = sub { "$_[0] called Test::baz" }; ... package Page { ... for ( $foo, $bar, $baz->() ) { # less characters, safe mecha +nism TR { TD { $_ }; TD { $c++}; } } ... }

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!

      • performance: One needs to reduce runtime as much as possible, like by memoization

      There's another package which implements the same DSL: Template::Declare. I benchmarked these two...

      Result:

      Rate template writer template 1659/s -- -20% writer 2073/s 25% --

      Not too bad... update: the difference, I guess, is mainly due to that I'm just implementing a wheel - without a car around it. But the wheel that has to be implemented is a dsl pragma, which would switch on/off various DSLs at compile time, even nested - think of SGML/SVG/XML/XUL/JS and whatnot, and each would make their proper syntax available as plain perl inside their current scope.

      • namespace polution: The effects of the embedded language must be contained to a lexical scope.

      To that end, my current code on github works as a pragma. The DSL routines are imported at compile time with use HTML::Writer, unimported with no HTML::Writer and handled via AUTOLOAD at runtime. Conflicting subroutines of the caller are saved and restored at no HTML::Writer, so there is no namespace pollution at all. Regarding inspection:

      I already wrote code for this in my Macro module (github only).

      Would you please provide the URL? I might learn something... ;-)

      perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'