my ($user, $workhrs0, $geo0, $workhrs1, $geo1) = ('NWIGER', '20', 'ASIA', '50', 'EURO'); query { package Table; WHERE ( ANDS ( user == $user, ORS ( ANDS ( workhrs > $workhrs0 , geo == 20 ), ORS ( $workhrs1 < workhrs, geo == $geo1 ) ) ) ); }; #### === B::Deparse of the Code: { package Table; use warnings; use strict; use feature 'say'; WHERE(ANDS(user() == $user, ORS(ANDS(workhrs() > $workhrs0, geo() == 20), ORS($workhrs1 < workhrs(), geo() == $geo1)))); } at d:/Users/lanx/vm_share/perl/Talks/DSL/2018_GPW/exp/SQL_abstract.pl line 51. === Tidy of deparsed Perl-code: { package Table; use warnings; use strict; use feature 'say'; WHERE( ANDS( user() == $user, ORS( ANDS( workhrs() > $workhrs0, geo() == 20 ), ORS( $workhrs1 < workhrs(), geo() == $geo1 ) ) ) ); } === Abstract Syntax Tree (simplified): :'WHERE' is ::Clause :'ANDS' is ::Joiner :'=' is ::Infix :'user' is ::Column :'NWIGER' is ::Placeholder ["\n ", "NWIGER"] :'ORS' is ::Joiner :'ANDS' is ::Joiner :'>' is ::Infix :'workhrs' is ::Column :'20' is ::Placeholder ["\n ", 20] :'=' is ::Infix :'geo' is ::Column :'20' is SCALAR :'ORS' is ::Joiner :'<' is ::Infix :'50' is ::Placeholder ["\n ", 50] :'workhrs' is ::Column :'=' is ::Infix :'geo' is ::Column :'EURO' is ::Placeholder ["\n ", "EURO"] === Produced SQL: WHERE ( user = ? AND ( ( workhrs > ? AND geo = 20 ) OR ( ? < workhrs OR geo = ? ) ) ) at d:/Users/lanx/vm_share/perl/Talks/DSL/2018_GPW/exp/SQL_abstract.pl line 59. #### package Table; use strict; use warnings; use Data::Dump qw/pp dd/; sub generator { my ($op, $class) = @_; sub { bless [$op, @_], $class} } sub column { bless [@_], 'Column' } BEGIN { my @clauses = qw/WHERE/; my @joiners = qw/ANDS ORS/; my @columns = qw/user workhrs geo/; use subs @clauses; use subs @joiners; use subs @columns; no strict 'refs'; *{$_} = generator($_, 'SQL::DSL::Column') for @columns; *{$_} = generator($_, 'SQL::DSL::Clause') for @clauses; *{$_} = generator($_, 'SQL::DSL::Joiner') for @joiners; } package main; use Data::Dump qw/pp dd/; use B::Deparse; use Perl::Tidy; use feature 'say'; use subs qw/WHERE ANDS ORS/; use subs qw/LE GE LT GT EQ NE/; #use constant {user =>1, workhrs=>2, geo=>3}; our $level; sub query (&) { my $code = shift; my $query = $code->(); my $AST = pp $query; $AST =~ s/\(\[/[/g; $AST =~ s/bless//g; warn "\n\n=== B::Deparse of the Code: \n", my $text = B::Deparse->new()->coderef2text($code); warn "\n\n=== Tidy of deparsed Perl-code: \n"; Perl::Tidy::perltidy( source => \$text); warn "\n\n=== Abstract Syntax Tree (simplified): \n"; $level=0; warn "\n\n=== Produced SQL: \n", walk($query)," "; } ; sub walk { my $ast = shift; local $level= $level+1; my $indent = " "x$level; my $ref = ref $ast; my @args = (); my $token = ""; if ($ref) { @args = @$ast; $token = shift @args; } else { $token = $ast; $ref = 'SCALAR'; } (my $type=$ref) =~ s/^SQL::DSL//; say $indent ,":'$token' is $type"; #pp \@args; my @list; for my $part (@args) { push @list, walk($part) } my $rendition; if ( my $render = $ref->can("render") ) { $rendition = $render->("\n$indent",$token,@list); } else { my $list = join ",", @list ; $rendition = "$token"; $rendition .= "\n$indent\[ $list \n$indent]" if $list; } return $rendition; } my ($user, $workhrs0, $geo0, $workhrs1, $geo1) = ('NWIGER', '20', 'ASIA', '50', 'EURO'); query { package Table; WHERE ( ANDS ( user == $user, ORS ( ANDS ( workhrs > $workhrs0 , geo == 20 ), ORS ( $workhrs1 < workhrs, geo == $geo1 ) ) ) ); }; package SQL::DSL::Infix; sub render { my ($indent,$op,$left,$right) = @_; return "$indent $left $op $right"; } package SQL::DSL::Joiner; sub render { my ($indent,$op,@list) = @_; $op =~s/S$//; my $render = join " $op ", @list ; return "$indent( $render $indent)"; } package SQL::DSL::Clause; use Data::Dump qw/pp dd/; sub render { my ($indent,$op,@list) = @_; return "$indent $op @list"; } package SQL::DSL::Column; use Data::Dump qw/pp dd/; use Scalar::Util qw/readonly/; sub ovl_binop { my ($op) = @_; return sub { for my $operand ( @_[0,1] ) { if (not readonly($operand) and not ref $operand) { $operand = bless [ $operand], "SQL::DSL::Placeholder"; } } my @operands = $_[2] ? @_[1,0] : @_[0,1]; # check swapflag # warn "$op readonly0:$_[0]" if readonly # warn "$op readonly1:$_[1]" if readonly $_[1]; #pp my $obj = bless [ $op ,@operands], "SQL::DSL::Infix"; return $obj; } } use overload "==" => ovl_binop("="), ">" => ovl_binop(">"), "<" => ovl_binop("<"), ; package SQL::DSL::Placeholder; use Data::Dump qw/pp dd/; our @bind_values=(); sub render { pp \@_; push @bind_values, $_[1]; return '?'; }