Skip to content

Commit 2408d23

Browse files
author
Chris White
committed
Languages can parse source text themselves
A language can now specify, using a package variable, that it wants the source text. An axk script can contain multiple Ln declarations, and the text after an Ln declaration and before the next can be given to that package as a single-quoted heredoc. See example in ex/l0.axk.
1 parent d263a08 commit 2408d23

File tree

4 files changed

+140
-8
lines changed

4 files changed

+140
-8
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ inc/
4242
/*.zip
4343

4444
# Capture::Tiny
45-
DEBUG
45+
DEBUG*

ex/l0.axk

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
L0
2+
3+
Some random text that isn't valid Perl!
4+
(*!@&$(*#O!@UJJ(O@()@)(
5+
6+
L1
7+
8+
say "In L1 at ", __FILE__, ':', __LINE__;
9+
10+
# vi: set ts=4 sts=4 sw=4 et ai ft=perl: #

lib/XML/Axk/Core.pm

Lines changed: 98 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ sub load_script_file {
5252

5353
# Load the given text, but do not execute it.
5454
# @param $self
55-
# @param $text {String} The source text
55+
# @param $text {String} The source text, **which load_script_text may modify.**
5656
# @param $fn {String} Filename to use in debugging messages
5757
# @param $add_Ln {boolean, default false} If true, add a Ln directive for the
5858
# current version if there isn't one in the script.
@@ -65,6 +65,14 @@ sub load_script_text {
6565
# Text to wrap around the script
6666
my ($leader, $trailer) = ('', '');
6767

68+
$text .= "\n"; # for consistency, e.g., so `axk -e 'L0'` won't crash.
69+
70+
# Prep the filename
71+
#$fn =~ s{\\}{\\\\}g; # This doesn't seem to be necessary based on
72+
# the regex given for #line in perlsyn.
73+
$fn =~ s{"}{-}g;
74+
# as far as I can tell, #line can't handle embedded quotes.
75+
6876
=pod
6977
7078
=head1 SPECIFYING THE AXK LANGUAGE VERSION
@@ -96,20 +104,103 @@ files.
96104
97105
=cut
98106

99-
unless($text =~ s{^\h*L\h*0*(\d+)\h*;?}{use XML::Axk::L$1;}mg) {
107+
my $has_lang;
108+
my $curr_trailer;
109+
my $lines_added = 0;
110+
111+
# Regex to match an Ln specification
112+
my $RE_Ln = qr{
113+
^\h*L\h* # has to start the line
114+
(?|
115+
(?:0*(\d+)) # digit form
116+
| (?:``(\w\+)) # alpha form, e.g., L``foo. I think ``
117+
) # is fairly unlikely in real text.
118+
\b\h*;? # permit trailers for ergonomics
119+
}mx;
120+
121+
while( $text =~ m/$RE_Ln/g ) {
122+
my @idxes=($-[0], $+[0]);
123+
my $lang = $1;
124+
#say "Match $lang in =$text= at ", join ',',@idxes;
125+
my $oldpos = pos($text);
126+
my $length_delta = 0; # how much to adjust pos($text) by
127+
#say "Old pos: ", $oldpos;
128+
129+
# Get line number in the original file
130+
my $orig_lineno = 1 + ( () = substr($text, 0, $idxes[0]) =~ /\n/g );
131+
# Thanks to ikegami, http://www.perlmonks.org/?node_id=968352
132+
$orig_lineno -= $lines_added;
133+
134+
# Ln must be followed by whitespace and a newline.
135+
# This is to keep the line numbering vaguely consistent.
136+
my ($removed) = (substr($text, $idxes[1]) =~ s/\A(\h*\n)//);
137+
138+
unless($removed) {
139+
# Tell the caller where in the source file the problem is
140+
eval "#line $orig_lineno \"$fn\"\n" .
141+
"die(\"L$lang indicator must be on its own line\");";
142+
die $@;
143+
}
144+
145+
$length_delta -= length($removed);
146+
147+
my $replacement = '';
148+
$has_lang = 1;
149+
150+
# End an existing capture if we're switching languages
151+
if($curr_trailer) {
152+
$replacement .= "\n$curr_trailer\n" .
153+
"#line $orig_lineno \"$fn\"\n";
154+
$curr_trailer='';
155+
++$lines_added;
156+
}
157+
158+
# Does this language parse the source text itself?
159+
my $want_text;
160+
eval "require XML::Axk::L$lang";
161+
die "Can't find language $lang: $@" if $@;
162+
do {
163+
no strict 'refs';
164+
$want_text = ${"XML::Axk::L${lang}::C_WANT_TEXT"};
165+
};
166+
167+
unless($want_text) { # Easy case: the script's code is still Perl
168+
$replacement .= "use XML::Axk::L$lang;\n";
169+
170+
} else { # Harder case: give the Ln the source text
171+
$curr_trailer =
172+
"AXK_EMBEDDED_SOURCE_DO_NOT_TYPE_THIS_YOURSELF_OR_ELSE";
173+
174+
my $following_lineno = $orig_lineno+1;
175+
# Number of first line of the text in that language
176+
$replacement .=
177+
"use XML::Axk::L$lang \"$fn\", $following_lineno, " .
178+
"<<'$curr_trailer';\n";
179+
}
180+
181+
if($replacement) {
182+
substr($text, $idxes[0], $idxes[1] - $idxes[0]) = $replacement;
183+
$length_delta += (length($replacement) - ($idxes[1] - $idxes[0]));
184+
}
185+
186+
if($length_delta) {
187+
#say "pos = $oldpos; Delta pos = $length_delta";
188+
pos($text) = $oldpos + $length_delta;
189+
}
190+
} #foreach lang
191+
192+
$text .= "\n$curr_trailer\n" if $curr_trailer;
193+
194+
unless($has_lang) {
100195
if($add_Ln) {
101196
$leader = "use XML::Axk::L1;\n"; # To be updated over time
102197
} else {
103198
croak "No language (Ln) specified in file $fn";
104199
}
105200
}
106201

107-
# Mark the filename for the sake of error messages.
108-
#$fn =~ s{\\}{\\\\}g; # This doesn't seem to be necessary based on
109-
# the regex given for #line in perlsyn.
110-
$fn =~ s{"}{-}g;
111-
# as far as I can tell, #line can't handle embedded quotes.
112202

203+
# Mark the filename for the sake of error messages.
113204
$leader .= ";\n#line 1 \"$fn\"\n";
114205
# Extra ; so the #line directive is in its own statement.
115206
# Thanks to https://www.effectiveperlprogramming.com/2011/06/set-the-line-number-and-filename-of-string-evals/

lib/XML/Axk/L0.pm

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env perl
2+
# XML::Axk::L0 - DUMMY axk language, version 0
3+
# Copyright (c) 2018 cxw42. All rights reserved. Artistic 2.
4+
# This is not a real axk language - it exists for testing.
5+
6+
package XML::Axk::L0;
7+
use XML::Axk::Base;
8+
9+
# Config
10+
our $C_WANT_TEXT = 1;
11+
12+
# Packages we invoke by hand
13+
require XML::Axk::Language;
14+
15+
# Import ========================================================= {{{1
16+
17+
sub import {
18+
#say "update: ",ref \&update, Dumper(\&update);
19+
my $target = caller;
20+
say "XAL0 run from $target:\n", Dumper(\@_);
21+
XML::Axk::Language->import(
22+
target => $target
23+
);
24+
my $class = shift;
25+
my ($fn, $lineno, $source_text) = @_;
26+
say "Got source text at $fn:$lineno:\n-----------------\n$source_text\n-----------------";
27+
} #import()
28+
29+
#}}}1
30+
1;
31+
# vi: set ts=4 sts=4 sw=4 et ai fo-=ro foldmethod=marker fdl=1: #

0 commit comments

Comments
 (0)