#!perl use warnings; use strict; package SQL_DSL; use Exporter 'import'; our @EXPORT_OK = qw/SQL WHERE/; use Carp; sub SQL (&;@) { # $SQL_DSL::IN_SQL is *not* required when using # SQL_DSL_Transform or SQL_DSL_Keyword croak "Already in SQL block" if $SQL_DSL::IN_SQL; local $SQL_DSL::IN_SQL = 1; print "Exec SQL\n"; # Debug shift->(@_) } sub WHERE (&;@) { croak "WHERE not in SQL block" unless $SQL_DSL::IN_SQL; print "Exec WHERE\n"; # Debug shift->(@_) } package LIKE; use Carp; sub AUTOLOAD { our $AUTOLOAD; croak "$AUTOLOAD not in SQL block" unless $SQL_DSL::IN_SQL; print "Exec $AUTOLOAD (@_)\n"; # Debug } 1; #### #!/usr/bin/env perl use warnings; use strict; # Version "one": #use SQL_DSL qw/SQL WHERE/; # Version "two": #use SQL_DSL_Keyword; # Version "three": #use SQL_DSL 'SQL'; #use Filter::PPI::Transform 'SQL_DSL_Transform'; my $x = '%abc%'; SQL { WHERE { print "Where: ",__PACKAGE__, "\n"; b LIKE $x }; }; WHERE {}; # dies b/c not in SQL { ... } #### Exec SQL Exec WHERE Where: main Exec LIKE::b (LIKE %abc%) WHERE not in SQL block at ... #### Exec SQL Exec WHERE Where: SQL_DSL Exec LIKE::b (LIKE %abc%) Can't call method "WHERE" without a package or object reference at ... #### #!perl package SQL_DSL_Keyword; use warnings; use 5.012; # for experimental pluggable keyword API use parent 'SQL_DSL'; use Carp; use Keyword::Simple; sub import { Keyword::Simple::define SQL => sub { ${$_[0]}=~s/^\s*\{/SQL_DSL::SQL { package SQL_DSL; / or croak "SQL keyword must be followed by a block"; }; } sub unimport { Keyword::Simple::undefine 'SQL'; } 1; #### #!perl package SQL_DSL_Transform; use warnings; use strict; use Params::Util qw/_INSTANCE/; use parent 'PPI::Transform'; sub new { shift->SUPER::new(@_) } sub document { my $self = shift; my $document = _INSTANCE(shift, 'PPI::Document') or return undef; my $elements = $document->find( sub { $_[1]->isa('PPI::Token::Word') or return ''; $_[1]->content eq 'SQL' or return ''; $_[1]->snext_sibling->isa('PPI::Structure::Block') or return ''; return 1; } ); return undef unless defined $elements; return 0 unless $elements; foreach my $e (@$elements) { my $n = PPI::Document->new(\"package SQL_DSL;"); for my $t (reverse $n->tokens) { $e->snext_sibling->first_token->insert_after($t->remove) or die; } } return scalar @$elements; } 1;