Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical

Best practice on escapes in interpolated strings

by wanna_code_perl (Pilgrim)
on Jul 29, 2013 at 11:01 UTC ( #1046837=perlquestion: print w/replies, xml ) Need Help??
wanna_code_perl has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks,

In some reporting code, I've decided to implement some simple markup to alleviate much of the tedium of including frequently used dynamic text in string outputs. It lets me turn something like this:

$obj->print(sprintf("Page %d - Section %d - %s", $obj->current_page, $obj->section->num, $obj->section->name, ) );

Into this (short codes shown here; they can also be spelled in full):

    $obj->print("Page ^(pg) - Section ^(sec:num) - ^(sec)");

(N.B.: I contrived this example so its purpose is immediately obvious. It still shows the expressiveness I want and bonus ability to re-use the same input string later with different results. The actual codes are extremely domain-specific.)

I've already written code for this that works, but my question is: what do you monks feel is the best syntax to allow escaping of a literal ^(foo) string? Suppose ^(foo) interpolates to 'BAR'. Here are the three options I thought of:

Option A - Backslash escapes

(Try to emulate Perl escapes, with limited success.)

BEFORE | AFTER | COMMENT -------------+--------+---------------- '^(foo)' | BAR | Regular use '\^(foo)' | ^(foo) | Escaped ^ "\^(foo)" | BAR | Perl squashes \^ | | in double quotes '\\^(foo)' | \^(foo)| Confused with '\BAR'? "\\^(foo)" | ^(foo) | Double quotes

Option B - ^^ escapes ^

(Similar to %% in printf)

BEFORE | AFTER | COMMENT -------------+--------+---------------- '^^(foo)' | ^(foo) | ^^ = escaped ^ '^^^(foo)' | ^BAR | ^^ = ^, ^(foo) '^^^^(foo)' | ^^BAR | ^^ = ^ twice '^ ^(foo)' | ^ BAR | No need to escape | ^ unless part of | ^(foo) match.

Option C - Special ^(...) code for literal carat

(Similar to HTML escapes or Pod C<$a E<lt>=E<gt> $b>, if a bit more concise.)

BEFORE | AFTER | COMMENT -------------+--------+----------------- '^(^)(foo)' | ^(foo) | ^(^) = escaped ^ '^^(foo)' | ^BAR | ^ literal if not | part of pattern '^(^)^(foo)' | ^BAR | Explicit escape '^^(^)(foo)' | ^^BAR | Or ^(^)^(^)^(foo)

I think most would agree that each of these invite potentially frustrating syntax (at least the errors are likely to be readily apparent), but I'd like to be as obvious as possible without a large documentation burden (while still supporting this sort of interpolation, of course).

A couple of other notes:

  • All interpolation patterns match m!\^\(\w+(?::\w+)?\)!
  • For (rare) cases where untrusted input or large sections of verbatim text must be included, there's a $obj->print_literal method (which is what $obj->print actually calls once it's completed the interpolation).
  • I don't need recommendations for String::Interpolate or existing parsing/templating systems. Recommend if you must, but please remember I'm looking for advice on semantics, not implementation.

Which syntax (including anything I haven't considered here) would you most prefer/most hate to use (and troubleshoot)? Why? I'd very much appreciate any relevant suggestions.

Replies are listed 'Best First'.
Re: Best practice on escapes in interpolated strings
by Skeeve (Vicar) on Jul 29, 2013 at 11:35 UTC

    I, for some timenow, do it lke this:

    "Page %(pg)d - Section %(sec)d - %(sec)s"

    In a first step I extract from all the "%(something)" the "something" leaving just a % so that the examp string becomes:

    "Page %d - Section %d - %s"

    At the same time I push the "something"s onto an array.

    I borrowed this syntax from python. The escaping should then simply be: Double the %:



        Thanks for this! Didn't even THINK of searching for that, but did it on my own.

Re: Best practice on escapes in interpolated strings
by Corion (Pope) on Jul 29, 2013 at 11:52 UTC

    Code-wise, the third option is the easiest to implement, at least if you pre-initialize your key-hash with a default:

    my %values= ( '^' => '^', ); ... s!\^\(([^)]+)\)!$values{ $1 }!g;

    Due to the selection of a default key of ^ with value ^, ^(^) gets replaced by ^ without any change to your code.

    Of course, this approach isn't immediately applicable to your situation because you seem to want to dispatch method calls. A really nasty approach here would be to implement a ^ method in your object that returns the value ^, but that brings us deep into obfuscated territory, and the superficial simplicity of the code will certainly mystify the casual onlooker:

    package My::Document; *{"My::Document::^"}= sub { "^" }; sub name { $_[0]->{name} }; sub fill_template { my( $self, $template )= @_; $template=~ s!\^\(([^)]+)\)!$self->$1()!ge; $template }; package main; my $t= bless { name => 'wanna_code_perl', } => My::Document; print $t->fill_template(<<TMPL); Hello ^(name). Escaped: ^(^). TMPL ;
Re: Best practice on escapes in interpolated strings (tt2/mojo)
by Anonymous Monk on Jul 29, 2013 at 11:44 UTC

    Recommend if you must, but please remember I'm looking for advice on semantics, not implementation.

    Mine existing implementations for semantics? Ha:D

    Text::Xslate::Syntax::Kolon is speedyfast, see Text::Xslate::Manual::FAQ, Xslate - Scalable template engine for Perl5

    I like Mojo::Template/TT2 deal where its MARKERattribute varname attributeMARKER as in

    [%- trim_prev_trail_whitespace -%] [%- trim_prev_whitespace %] [% trim_trail_whitespace | html -%] <%# comment line then trailing whitespace trimmed =%> <%== Perl expression, replaced with XML escaped result %> <: $textarea | html :>
Re: Best practice on escapes in interpolated strings
by Dallaylaen (Hermit) on Jul 29, 2013 at 16:16 UTC

    I've recently stumbled upon String::Format. Although I haven't used it yet, I believe it to be a possible solution for such cases. In a nutshell:

    #!/usr/bin/perl -w use strict; use String::Format qw(stringf); print stringf( "%a->%o->%a\n", a => "apples", o => "oranges" );

    I don't know how fast it is, though.

    UPDATE: I like the %(foo) solution even more!

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1046837]
Front-paged by Corion
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (4)
As of 2018-06-23 03:16 GMT
Find Nodes?
    Voting Booth?
    Should cpanminus be part of the standard Perl release?

    Results (125 votes). Check out past polls.