DMS-Parser
view release on metacpan or search on metacpan
lib/DMS/Parser.pm view on Meta::CPAN
}
my $c = substr($src, $p, 1);
if ($c eq '"') {
$self->{pos} = $p + 1;
# SPEC §Unicode normalization: re-NFC after escape decoding.
return $out !~ /[^\x00-\x7F]/ ? $out : _NFC($out);
}
if ($c eq "\n" || $c eq "\r") {
$self->{pos} = $p;
$self->_die("strings cannot span lines");
}
# $c is '\\' â handle the escape, then resume the bulk scan.
$p++; # past the backslash
if ($p >= $len) {
$self->{pos} = $p;
$self->_die("unterminated escape");
}
my $esc = substr($src, $p, 1);
$p++;
if ($esc eq '"') { $out .= '"'; }
elsif ($esc eq '\\') { $out .= '\\'; }
elsif ($esc eq 'n') { $out .= "\n"; }
elsif ($esc eq 't') { $out .= "\t"; }
elsif ($esc eq 'r') { $out .= "\r"; }
elsif ($esc eq 'b') { $out .= "\b"; }
elsif ($esc eq 'f') { $out .= "\f"; }
elsif ($esc eq 'u' || $esc eq 'U') {
# _read_hex_codepoint reads from $self->{pos}; sync it first.
$self->{pos} = $p;
$out .= $self->_read_hex_codepoint($esc eq 'u' ? 4 : 8);
$p = $self->{pos};
}
else {
$self->{pos} = $p;
$self->_die("invalid escape '\\$esc'");
}
pos($src) = $p;
}
}
sub parse_literal_string_value {
my $self = shift;
my ($sl, $sls, $sp) = ($self->{line}, $self->{line_start}, $self->{pos});
$self->{pos}++;
my $out = '';
while (1) {
if ($self->_eof) {
die $self->_err_at($sl, $sls, $sp, "unterminated string");
}
my $c = $self->_peek;
$self->_die("strings cannot span lines") if $c eq "\n" || $c eq "\r";
if ($c eq "'") { $self->{pos}++; return $out; }
$self->{pos}++;
$out .= $c;
}
}
sub _read_hex_codepoint {
my ($self, $n) = @_;
my $rest = substr($self->{src}, $self->{pos});
$self->_die("expected $n hex digits in unicode escape") if length($rest) < $n;
my $hex = substr($rest, 0, $n);
$self->_die("invalid hex in unicode escape: $hex") if $hex !~ /^[0-9a-fA-F]+$/;
my $v = hex($hex);
# SPEC: U+0000 is forbidden anywhere in DMS source, including via
# escape decoding. ` ` / `\U00000000` must not slip through.
if ($v == 0) {
$self->_die("\\u0000 escape forbidden");
}
if ($v >= 0xD800 && $v <= 0xDFFF) {
$self->_die(sprintf("surrogate codepoint U+%04X in escape", $v));
}
if ($v > 0x10FFFF) {
$self->_die("unicode escape is not a scalar value");
}
$self->{pos} += $n;
return chr($v);
}
# Heredocs
sub parse_heredoc_basic {
my $self = shift;
$self->{pos} += 3;
my $label = $self->_parse_heredoc_label;
my $mods = $self->_parse_heredoc_modifiers;
$self->_skip_inline_ws;
if (!($self->_consume_eol || $self->_eof)) {
$self->_die("heredoc opener must be followed by end of line");
}
my $terminator = length($label) ? $label : '"""';
my $body = $self->_collect_heredoc_body($terminator);
# SPEC §basic-string escapes: surrogate codepoints (U+D800..U+DFFF)
# and U+0000 are not valid in `\uXXXX` / `\UXXXXXXXX` escapes.
# Basic-heredoc bodies are kept raw, so we validate the rules by
# scanning the body for offending escape sequences.
_validate_heredoc_basic_escapes($body);
my $stripped = _strip_indent_and_continuations($body, 1);
my $out;
eval { $out = _apply_modifiers($stripped, $mods); };
if ($@) { my $msg = $@; chomp $msg; $self->_die($msg); }
$self->_record_form({
string_form => {
kind => 'heredoc',
flavor => 'basic_triple',
label => (length($label) ? $label : undef),
modifiers => [ map { { name => $_->{name}, args => $_->{args} } } @$mods ],
},
});
# SPEC §Unicode normalization: re-NFC after escape decoding.
return $out !~ /[^\x00-\x7F]/ ? $out : _NFC($out);
}
# SPEC §basic-string escapes: a `\uXXXX` / `\UXXXXXXXX` escape whose
# decoded value falls in the surrogate range U+D800..U+DFFF is not a
# Unicode scalar and is a parse error. Likewise U+0000 is forbidden.
# Basic-heredoc body lines are collected raw, so we validate the same
# rules by scanning the body for offending escape sequences.
sub _validate_heredoc_basic_escapes {
my ($body) = @_;
for my $ln (@{$body->{lines}}) {
my $text = $ln->{text};
my $len = length($text);
my $i = 0;
while ($i < $len) {
if (substr($text, $i, 1) eq '\\') {
# find run of consecutive backslashes
my $j = $i;
while ($j < $len && substr($text, $j, 1) eq '\\') { $j++; }
my $run = $j - $i;
if ($run % 2 == 1 && $j < $len) {
my $intro = substr($text, $j, 1);
my $n = ($intro eq 'u') ? 4 : ($intro eq 'U') ? 8 : 0;
if ($n > 0 && $j + 1 + $n <= $len) {
( run in 0.673 second using v1.01-cache-2.11-cpan-9581c071862 )