in reply to Re^3: 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)

> because it is quite slow and because of inherent namespace pollution

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

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!

Replies are listed 'Best First'.
Re^5: Which internal DSL are there in Perl? (Domain Specific Languages - Part 1)
by shmem (Chancellor) on Aug 04, 2017 at 10:28 UTC
    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.
        packages can't surround each other, your packages Test and Page are on the same level.

        I know that packages don't nest, and that namespaces are not organized in a hierarchy. What I am after is information about when package switching occurs, and after switching, whence. This information must be known to the compiler to restore the previous package's scope after the current package's scope ends:

        package Foo { ... package Bar { ... # I want to know 'whence' here }; ... # package Foo resumed. ... # the compiler knows about the package to be resumed. ... # how can I use that information inside 'Bar'? };
        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
        The ellipses are enough to make that quote inaccurate. This is a package with a block, which means it continues until the end of the block. Which means that at the end of the block, things go back to being in the surrounding package.
      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!

        > of course TIMTOWTDI.

        Autoload rules! =)

        package OuterPkg; use strict; use warnings; use Carp qw/cluck/; sub foo { my $sub =(caller(0))[3]; $"=","; return "Calling $sub(@_)"; } use Import2Package 'DTD' => 'myDSL'; package myDSL { print TABLE { TR { TD {1}, TD{foo()}, } } } # TABLE(); # not visible in outer Package

        exec "perl OuterPkg.pl" unless caller(); use strict; use warnings; use Carp qw/cluck/; package Import2Package; sub delegate_subs { my ( $pkg, $super )=@_; use vars '$AUTOLOAD'; no strict 'refs'; *{${pkg}."::AUTOLOAD"} = sub { my $delegate = $AUTOLOAD; $delegate =~ s/.*:://; # strip packagename goto &{"${super}::${delegate}"}; } } sub install_vocab { my ( $pkg, $super ) = @_; my @vocab = qw/TABLE TR TD/; my $level = 0; no strict 'refs'; for my $CMD (@vocab) { *{"${pkg}::$CMD"} = sub (&) { $level++; my @inside = $_[0]->(); $level--; my $indent = "\n" . "\t" x $level; my $tag =lc($CMD); return join "",map {"<$tag>$_</$tag>"} @inside; } } *{"${pkg}::render"} = sub { print @_ }; } sub import { my ($self, $dtd, $pkg) =@_; my $super = (caller())[0]; delegate_subs $pkg => $super; install_vocab $pkg, $super; } 1;

        <table><tr><td>1</td></tr><tr><td>Calling OuterPkg::foo()</td></tr></t +able>

        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!

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

        Yes, that's a good way to go. There are so many paths, so much OWTDI, and to get a good one, so many things want to be thinked about... Thanks.

        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
      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!

        why not
        for ( $foo, $bar, $baz->() ) { # less characters, safe mecha +nism

        Yes of course, but for the sake of TIMTOWTDI, transparent calls of functions/methods known in the scope which uses the DSL should be possible. And yes, the notation Page->baz() is a kludge. Need something else.

        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re^5: Which internal DSL are there in Perl? (HTML::Writer as pragma)
by shmem (Chancellor) on Aug 07, 2017 at 09:00 UTC
    • 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'