1
# -*- CPERL -*-
2
# /=====================================================================\ #
3
# | listings | #
4
# | Implementation for LaTeXML | #
5
# |=====================================================================| #
6
# | Part of LaTeXML: | #
7
# | Public domain software, produced as part of work done by the | #
8
# | United States Government & not subject to copyright in the US. | #
9
# |---------------------------------------------------------------------| #
10
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
11
# | http://dlmf.nist.gov/LaTeXML/ (o o) | #
12
# |---------------------------------------------------------------------| #
13
# | Minor modifications by | #
14
# | Tim Vismor | #
15
# | https://vismor.com | #
16
# | | #
17
# | Search for TDV to see the changes. | #
18
# \=========================================================ooo==U==ooo=/ #
19
20
#**********************************************************************
21
package LaTeXML::Package::Pool;
22
use strict;
23
use LaTeXML::Package;
24
use LaTeXML::Util::KeyVal;
25
26
#======================================================================
27
# To the extent we succeed in doing all the pretty-printing...
28
# It rather seems that preserving a raw, unformatted, copy of the code
29
# would be a Useful thing, and in keeping with XML.
30
# Wouldn't you want to see the pretty print, but cut&paste the plain code?
31
# This may eventually need some schema support...
32
33
#======================================================================
34
# 4.2 Typesetting listings
35
#======================================================================
36
37
# Set various Listings keys
38
DefPrimitive('\lstset RequiredKeyVals:LST', sub { lstActivate($_[1]); return; });
39
40
our $EMPTY_CATTABLE=LaTeXML::State->new(catcodes=>'none');
41
42
DefMacro('\lstinline OptionalKeyVals:LST', sub {
43
my($gullet,$keyvals)=@_;
44
my $mouth = $gullet->getMouth;
45
my ($init,$body);
46
{ local $STATE = $EMPTY_CATTABLE;
47
$init = $mouth->readToken;
48
$body = $mouth->readTokens($init); }
49
$STATE->getStomach->bgroup;
50
lstActivate($keyvals);
51
my @expansion = lstProcessInline(ToString($body));
52
$STATE->getStomach->egroup;
53
@expansion; });
54
55
sub lstProcessInline {
56
Invocation(T_CS('\@listings@inline'), lstProcess('inline',@_)); }
57
58
DefConstructor('\@listings@inline {}',
59
"<ltx:text class='listing'>#1</ltx:text>",
60
reversion=>'\lstinline#1#2#3#2'); # ??????
61
62
# Not a regular environment, since we're going to read the body verbatim!
63
DefMacroI(T_CS('\begin{lstlisting}'),
64
LaTeXML::Package::parseParameters('OptionalKeyVals:LST','\begin{lstlisting}'),
65
sub {
66
my($gullet,$keyvals)=@_;
67
$STATE->getStomach->bgroup;
68
AssignValue(current_environment=>'lstlisting');
69
my $text = join('',$gullet->getMouth->readRawLines("\\end{lstlisting}"));
70
$text =~ s/^\s*?\n//s;
71
lstActivate($keyvals);
72
my @expansion = lstProcessBlock(lstGetTokens('name'),$text);
73
$STATE->getStomach->egroup;
74
@expansion; });
75
76
DefMacro('\lstinputlisting OptionalKeyVals:LST Semiverbatim', sub {
77
my($gullet,$keyvals,$file)=@_;
78
my $path = FindFile($file);
79
my $text;
80
if( $path && open(LST, $path)){
81
{ local $/ = undef;
82
$text = <LST>;
83
close(LST); }}
84
else {
85
Error("unexpected:$file Couldn't read listings file $file: $!"); }
86
$STATE->getStomach->bgroup;
87
lstActivate($keyvals);
88
my @expansion = lstProcessBlock($file,$text);
89
$STATE->getStomach->egroup;
90
@expansion; });
91
92
NewCounter('lstlisting','document',idprefix=>'LST');
93
94
sub lstProcessBlock {
95
my($name,$text)=@_;
96
# Hmm.. should locally define \lstname to be either name or the file...
97
my @body = (Invocation(T_CS('\@listings@inner'),lstProcess('block',$text)));
98
my($numbered,$labelled,$caption,$x);
99
if(($x = lstGetTokens('caption')) && scalar($x->unlist)){
100
my @t = $x->unlist;
101
my @tc=();
102
if(Equals($t[0],T_OTHER('['))){
103
while(!Equals($t[0],T_OTHER(']'))){ push(@tc,shift(@t)); }}
104
$numbered=1;
105
$caption=Invocation(T_CS('\caption'),(@tc ? Tokens(@tc):undef),Tokens(@t)); }
106
elsif(($x = lstGetTokens('title')) && scalar($x->unlist)){
107
$caption =Invocation(T_CS('\caption'),undef,$x); }
108
if(($x = lstGetTokens('label')) && scalar($x->unlist)){
109
$labelled=1;
110
unshift(@body,Invocation(T_CS('\label'),$x)); }
111
if($caption){
112
if(lstGetLiteral('captionpos') eq 't'){
113
unshift(@body,$caption); }
114
else {
115
push(@body,$caption); }}
116
117
# We go a bit (a bit too far?) to try to treat this as a separate Para level object
118
# (if with captions or labelled),
119
# or as an in-block item (within a logical paragraph)
120
(
121
($numbered || $caption || $labelled ? (T_CS('\par')):()),
122
T_BEGIN,
123
($name ? (T_CS('\def'),T_CS('\lstname'),T_BEGIN,$name->unlist,T_END) : ()),
124
Invocation(($numbered ? T_CS('\@listings')
125
: ($caption || $labelled ? T_CS('\@@listings')
126
: T_CS('\@@listings@inblock'))), Tokens(@body)),
127
T_END); }
128
129
DefConstructor('\@listings@inner {}', "<ltx:tabular>#1</ltx:tabular>");
130
# Or new listing element?
131
DefConstructor('\@listings {}',
132
"<ltx:listing xml:id='#id' refnum='#refnum'>#1</ltx:listing>",
133
properties=>sub { RefStepCounter('lstlisting'); });
134
DefConstructor('\@@listings {}',
135
"<ltx:listing xml:id='#id'>#1</ltx:listing>",
136
properties=>sub { RefStepID('lstlisting'); });
137
DefConstructor('\@@listings@inblock {}',
138
"<ltx:listingblock xml:id='#id'>#1</ltx:listingblock>",
139
properties=>sub { RefStepID('lstlisting'); });
140
141
#======================================================================
142
# Managing the sets of keyvals that compose a Listings Style or Language.
143
#======================================================================
144
# Assign (locally) all values or effects from a Listings keyvals
145
# Note that we operate on the Pairs form of keyvals to preserve order, repetition
146
#
147
# LST_CHARACTERS hash (letter|digit|other) => hash : charre=>1
148
# LST_CLASSES hash classname => hash : begin, end => Tokens
149
# and some extra: index=>indexclassname, escape=>0|1, eval=>0|1, ...
150
# LST_WORDS hash word => hash : class=>classname, index=>indexclassname
151
# LST_DELIMTERS hash open => hash: regexp=>re, close => re, classname?
152
153
foreach my $table (qw(LST_CHARACTERS LST_CLASSES LST_WORDS LST_DELIMITERS)){
154
AssignValue($table=>{}); }
155
156
sub lstActivate {
157
my($kv)=@_;
158
if($kv){
159
# We will construct distillations of the various keyword, delimiter, etc data
160
# These tables will sit in the current binding, but we need to copy the data from previous bindings
161
# to get the effect of grouping
162
# Each table is a hash of hashes.
163
foreach my $table (qw(LST_CHARACTERS LST_CLASSES LST_WORDS LST_DELIMITERS)){
164
my %data = ();
165
if(my $prev = LookupValue($table)){
166
map( $data{$_} = {%{$$prev{$_}}}, keys %$prev); }
167
AssignValue($table => {%data}); }
168
# Now start scanning the keywords, in order, and activate their effects.
169
my @pairs = $kv->getPairs();
170
while(@pairs){
171
my($key,$val)=(shift(@pairs),shift(@pairs));
172
$val = lstUnGroup($val);
173
my $cs = T_CS('\lst@@'.$key);
174
if(LookupDefinition($cs)){
175
$val = LookupValue('KEYVAL@LST@'.$key.'@default') unless $val;
176
# Done for effect.
177
Digest(Tokens($cs,($val ? $val->unlist : Tokens()) ,T_CS('\end'))); }
178
else {
179
AssignValue('LST@'.$key=>$val); }}}}
180
181
#----------------------------------------------------------------------
182
# Various helpers for dealing with the arguments to options.
183
sub lstUnGroup { # Strip outer {}, if any
184
my($tokens)=@_;
185
if($tokens){
186
my @t = $tokens->unlist;
187
if(Equals($t[0],T_BEGIN) && Equals($t[$#t],T_END)){
188
$tokens = Tokens(@t[1..$#t-1]); }}
189
$tokens; }
190
191
sub lstSplit {
192
my($stuff)=@_;
193
my $string = ToString(lstUnGroup($stuff));
194
$string =~ s/%.*?\n\s*//sg;
195
$string =~ s/\s+//sg;
196
split(/,/,$string); }
197
198
# Strip of TeX's quoting.
199
sub lstDeslash {
200
if(my $string = $_[0]){
201
$string = ToString($string);
202
$string =~ s/^\\(.)/$1/g; # Strip off TeX's "quoting"
203
$string; }}
204
205
# Convert a string of TeX chars to a regexp to match it.
206
sub lstRegexp {
207
my($chars)=@_;
208
if(my $string = lstDeslash($_[0])){
209
$string =~ s/([\!\@\#\$\%\^\&\*\(\)\_\-\+\{\}\[\]\\\<\>\?\/])/\\$1/g; # Put back for Perl.
210
$string; }}
211
212
#----------------------------------------------------------------------
213
# A rather bizarro set of keyword value parsing bits.
214
# Perhaps should be handled by the keyval types themselves?
215
sub lstGetLiteral {
216
my $v = ToString(LookupValue('LST@'.$_[0]));
217
$v = $1 if $v =~ /^\{(.*?)\}$/;
218
$v; }
219
sub lstGetBoolean { lstGetLiteral($_[0]) eq 'true'; }
220
sub lstGetNumber { my $n =LookupValue('LST@'.$_[0]); ($n ? $n->valueOf : 0); }
221
222
sub lstGetTokens {
223
if(my $v = LookupValue('LST@'.$_[0])){
224
lstUnGroup($v); }
225
else {
226
Tokens(); }}
227
228
#======================================================================
229
# Support for managing classes, delimiters and such.
230
231
sub lstClassName {
232
my($class,$n)=@_;
233
$n = 1 unless $n;
234
$n = $n->valueOf if ref $n;
235
$n += lstGetNumber('classoffset');
236
$class . ($n <= 1 ? '' : $n); }
237
238
# Define properties of a Class (comments, strings, etc)
239
sub lstSetClassStyle {
240
my($class,$style,%props)=@_;
241
my $classes = LookupValue('LST_CLASSES');
242
if($style){
243
my $stylestring = ToString($style);
244
if($stylestring =~ s/style(\d*)$/s$1/){ # If names a style, convert it into the class name
245
delete $$classes{$class}{begin}; # remove explicit styling
246
$props{class}=$stylestring; } # add indirect to class.
247
else {
248
delete $$classes{$class}{class};
249
$props{begin}=$style; }} # Otherwise, presumably TeX
250
map($$classes{$class}{$_}=$props{$_}, keys %props);
251
return; }
252
253
# Specify a set of words belonging to a class
254
sub lstSetClassWords {
255
my($class,$words,$prefix)=@_;
256
# First delete existing words
257
my $wordslist = LookupValue('LST_WORDS');
258
foreach my $word (keys %$wordslist){
259
delete $$wordslist{$word}{class} if $$wordslist{$word}{class} eq $class; }
260
lstAddClassWords($class,$words,$prefix); }
261
262
sub lstAddClassWords {
263
my($class,$words,$prefix)=@_;
264
my $wordslist = LookupValue('LST_WORDS');
265
foreach my $word (lstSplit($words)){
266
$word = $prefix.$word if $prefix;
267
$$wordslist{$word}{class} = $class unless $$wordslist{$word}{class}; }
268
return; }
269
270
sub lstDeleteClassWords {
271
my($class,$words,$prefix)=@_;
272
my $wordslist = LookupValue('LST_WORDS');
273
foreach my $word (lstSplit($words)){
274
$word = $prefix.$word if $prefix;
275
delete $$wordslist{$word}{class} if $$wordslist{$word}{class} eq $class; }
276
return; }
277
278
# This probably needs a different way of decoding $type.
279
# General: b,d,l,s,n (+ i)
280
# String: b,d,m,bd (backslash, doubled, matlab-like(?) or backslash or doubled)
281
# Need to pull out the $delims decoding, to allow deleting delimiters.
282
# Recognized keys:
283
# recursive : allows keywords, comments & strings inside
284
# cummulative : the effects are cummulative (?)
285
# nested : allows comments to be nested
286
sub lstAddDelimiter {
287
my($kind,$type,$style,$delims,%keys)=@_;
288
my $delimlist = LookupValue('LST_DELIMITERS');
289
$type = ToString($type);
290
my $invisible = ($type =~ s/i//);
291
my $quoted;
292
my($open,$close);
293
if(($type eq 's')||($type eq 'n')){
294
# Here's the goofy thing: there may or may be {} in delimiters;
295
# And, when there's 2 delimiters, it could even be is: open}{close
296
# we'll hope there're no extra braces!
297
# If type eq 'n', comments are allowed to nest!!!
298
my @t = grep(!Equals($_,T_BEGIN),$delims->unlist); # Remove any T_BEGIN
299
my @t1=();
300
if(scalar(@t)==2){
301
@t1=($t[0]); @t=($t[1]); }
302
else {
303
while(@t && !Equals($t[0],T_END)){ push(@t1,shift(@t)); }
304
@t = grep(!Equals($_,T_END),@t);} # Remove any remaining T_END
305
$open = Tokens(@t1);
306
$close = Tokens(@t); }
307
else {
308
$open = $delims;
309
$close = $open; }
310
311
my $openre = lstRegexp($open);
312
my $closere = lstRegexp($close);
313
if($type eq 'b'){ # Balanced; same delim open & close; but not when slashed
314
$closere = "(?<!\\\\)$openre";
315
$quoted = "\\$openre"; }
316
elsif($type eq 'd'){ # Doubled: same delim open & close; but not when doubled.
317
$closere = "(?<!$openre)$openre(?!$openre)";
318
$quoted = $openre.$openre; }
319
elsif($type eq 'bd'){ # Doubled: same delim open & close; not when doubled OR slashed
320
$closere = "(?<!\\\\|$openre)$openre(?!$openre)";
321
$quoted = "\\$openre|$openre$openre"; }
322
elsif($type eq 'l'){ # Line: close is till end of line
323
$close = undef;
324
$closere = "(?=\n)"; }
325
elsif($type eq 's'){} # String: different open & close
326
elsif($type eq 'n'){ $keys{nested}=1; } # like String, but allows nesting!!!
327
328
if(my $openstring = lstDeslash($open)){
329
# print STDERR "DELIMITER: \"".ToString($delims)."\" => ".ToString($open)." ; open=$openre close=>$closere\n";
330
# The styling can be a class name, or markup
331
my $class;
332
my $stylestring = ToString($style);
333
if($stylestring =~ s/style(\d*)$/s$1/){ # Names the style associated with a class.
334
$class = $stylestring; }
335
else { # Otherwise, assume it is markup.
336
$class = $kind.ToString($open).ToString($close); # Create an artificial class for this delimiter.
337
lstSetClassStyle($class,undef,begin=>$style); }
338
if(!$invisible){
339
my $oldclass=$class;
340
$class = $class.ToString($open).ToString($close); # Create an artificial class for this delimiter.
341
lstSetClassStyle($class,undef,begin=>$open,end=>$close, class=>$oldclass); }
342
# NOT DONE:
343
# invisibility of the whole delimited expression
344
# nestability.
345
$$delimlist{$openstring}={open=>$openre, close=>$closere, class=>$class, quoted=>$quoted, %keys};
346
}
347
return; }
348
349
# Set character classes
350
sub lstSetCharacterClass {
351
my($class,$chars)=@_;
352
my $charslist = LookupValue('LST_CHARACTERS');
353
foreach my $char ($chars->unlist){
354
$char = lstRegexp($char);
355
delete $$charslist{letter}{$char};
356
delete $$charslist{digit}{$char};
357
delete $$charslist{other}{$char};
358
$$charslist{$class}{$char}=1; }
359
return; }
360
361
#======================================================================
362
# 4.3 Space and placement
363
#======================================================================
364
# Ignorable
365
DefKeyVal('LST','float',''); # [*] t,b,p,h [or defaults?]
366
DefKeyVal('LST','floatplacement',''); # t,b,p
367
DefKeyVal('LST','aboveskip','Dimension');
368
DefKeyVal('LST','belowskip','Dimension');
369
DefKeyVal('LST','lineskip','Dimension');
370
DefKeyVal('LST','boxpos',''); # b,c,t
371
372
#======================================================================
373
# 4.4 Printed range
374
#======================================================================
375
# Seemingly handled....
376
DefKeyVal('LST','print','', 'true');
377
DefKeyVal('LST','firstline','Number');
378
DefKeyVal('LST','lastline','Number');
379
DefKeyVal('LST','showlines','','true');
380
DefKeyVal('LST','emptylines',''); # NOTE: NOT YET HANDLED.
381
DefKeyVal('LST','gobble','Number');
382
383
#======================================================================
384
# 4.5 Language and styles
385
#======================================================================
386
# Define a Style being a shorthand for a set of Listings keyvals
387
# \lstdefinestyle{stylename}{keys}
388
DefPrimitive('\lstdefinestyle{} RequiredKeyVals:LST',sub {
389
my($stomach,$style,$keyvals)=@_;
390
$style = uc(ToString(lstUnGroup($style)));
391
$style =~ s/\s+//g;
392
AssignValue('LST@STYLE@'.$style=>$keyvals); });
393
394
DefKeyVal('LST','style','');
395
DefMacro('\lst@@style Until:\end', sub {
396
my($gullet,$style)=@_;
397
if($style = uc(ToString(lstUnGroup($style)))){
398
$style =~ s/\s+//g;
399
if(my $values = LookupValue('LST@STYLE@'.$style)){
400
lstActivate($values); }
401
else {
402
Warn("expected:$style No listings style $style found"); }}
403
return; });
404
405
sub lstActivateLanguage {
406
my($language,$dialect)=@_;
407
$language = uc(ToString($language)); $language =~ s/\s+//g;
408
if($language){
409
$dialect = LookupValue('LSTDD@'.$language) unless $dialect && $dialect->unlist;
410
my $name = 'LST@LANGUAGE@'.$language;
411
if($dialect && $dialect->unlist){
412
$dialect = uc(ToString($dialect)); $dialect =~ s/\s+//g;
413
$name .= '$'.$dialect; }
414
if(my $values = LookupValue($name)){
415
lstActivate($values); }
416
else {
417
Warn("expected:$name No listings language $language found"); }}}
418
419
DefKeyVal('LST','language','');
420
DefMacro('\lst@@language [] Until:\end', sub {
421
lstActivateLanguage($_[2],$_[1]); return; });
422
DefKeyVal('LST','alsolanguage','');
423
DefMacro('\lst@@alsolanguage [] Until:\end', sub {
424
lstActivateLanguage($_[2],$_[1]); return; });
425
426
DefKeyVal('LST','defaultdialect','');
427
DefMacro('\lst@@defaultdialect[] Until:\end', sub {
428
my($gullet,$dialect,$language)=@_;
429
$language = uc(ToString($language)); $language =~ s/\s+//g;
430
AssignValue('LSTDD@'.$language => $dialect); });
431
432
DefKeyVal('LST','printpod','','true'); # NOTE: NOT YET HANDLED
433
434
DefKeyVal('LST','usekeywordsintag','','true'); # NOTE: NOT YET HANDLED; I don't even understand it
435
DefKeyVal('LST','tagstyle','');
436
DefMacro('\lst@@tagstyle Until:\end', sub {
437
lstSetClassStyle('tags',$_[1]); });
438
DefKeyVal('LST','markfirstintag',''); # NOTE: NOT YET HANDLED; I don't even understand it
439
440
DefKeyVal('LST','makemacrouse','','true'); # NOTE: NOT YET HANDLED
441
442
#======================================================================
443
# 4.6 Appearance
444
#======================================================================
445
DefKeyVal('LST','basicstyle','');
446
447
DefKeyVal('LST','identifierstyle','');
448
DefMacro('\lst@@identifierstyle Until:\end', sub {
449
lstSetClassStyle('identifiers',$_[1]); });
450
451
DefKeyVal('LST','commentstyle','');
452
DefMacro('\lst@@commentstyle Until:\end', sub {
453
lstSetClassStyle('comments',$_[1]); });
454
455
DefKeyVal('LST','stringstyle','');
456
DefMacro('\lst@@stringstyle Until:\end', sub {
457
lstSetClassStyle('strings',$_[1]); });
458
459
DefKeyVal('LST','keywordstyle','');
460
DefMacro('\lst@@keywordstyle [Number] OptionalMatch:* Until:\end', sub {
461
lstSetClassStyle(lstClassName('keywords',$_[1]),$_[3], uppercase=>$_[2]); });
462
DefKeyVal('LST','ndkeywordstyle','');
463
DefMacro('\lst@@ndkeywordstyle Until:\end', sub {
464
lstSetClassStyle('keywords2',$_[1]); });
465
466
DefKeyVal('LST','classoffset','Number');
467
468
DefKeyVal('LST','texcsstyle','');
469
DefMacro('\lst@@texcsstyle OptionalMatch:* [Number] Until:\end', sub {
470
lstSetClassStyle(lstClassName('texcss',$_[2]),$_[3], slash=>$_[1]); });
471
DefKeyVal('LST','directivestyle','');
472
DefMacro('\lst@@directivestyle Until:\end', sub {
473
lstSetClassStyle('directives',$_[1]); });
474
475
DefKeyVal('LST','emph','');
476
DefMacro('\lst@@emph [Number] Until:\end', sub {
477
lstSetClassWords(lstClassName('emph',$_[1]),$_[2]); });
478
DefKeyVal('LST','moreemph','');
479
DefMacro('\lst@@moreemph [Number] Until:\end', sub {
480
lstAddClassWords(lstClassName('emph',$_[1]),$_[2]); });
481
DefKeyVal('LST','deleteemph','');
482
DefMacro('\lst@@deleteemph [Number] Until:\end', sub {
483
lstDeleteClassWords(lstClassName('emph',$_[1]),$_[2]); });
484
DefKeyVal('LST','emphstyle','');
485
DefMacro('\lst@@emphstyle [Number] Until:\end', sub {
486
lstSetClassStyle(lstClassName('emph',$_[1]),$_[2]); });
487
488
489
DefKeyVal('LST','delim','');
490
# \lst@delim=**[type][style]{delim}{delim2_if_needed}
491
# * allow keywords, comments & strings inside
492
# * effects are cummulative
493
DefMacro('\lst@@delim OptionalMatch:* OptionalMatch:* [] [] Until:\end', sub {
494
# clear delimiters, first ???
495
lstAddDelimiter('delimiter',$_[3],$_[4],$_[5],
496
($_[1] ? (recurse=>1):()),
497
($_[2] ? (cummulative=>1):())); });
498
DefKeyVal('LST','moredelim','');
499
DefMacro('\lst@@moredelim OptionalMatch:* OptionalMatch:* [] [] Until:\end', sub {
500
lstAddDelimiter('delimiter',$_[3],$_[4],$_[5],
501
($_[1] ? (recurse=>1):()),
502
($_[2] ? (cummulative=>1):())); });
503
504
#======================================================================
505
# 4.7 Getting characters right.
506
#======================================================================
507
DefKeyVal('LST','extendedchars','','true');
508
DefMacro('\lst@@extendedchars Until:\end',sub {
509
my @chars = map(UTF($_), 128..255);
510
my $charslist = LookupValue('LST_CHARACTERS');
511
if(ToString($_[1]) eq 'true'){
512
foreach my $char (@chars){
513
$$charslist{letter}{$char}=1; }}
514
else {
515
foreach my $char (@chars){
516
delete $$charslist{letter}{$char}; }}
517
return; });
518
DefKeyVal('LST','inputencoding',''); # Ignorable?
519
DefKeyVal('LST','upquote','','true'); # Ignorable?
520
DefKeyVal('LST','tabsize','Number');
521
DefKeyVal('LST','showtabs','','true'); # NOTE: Not yet handled
522
DefKeyVal('LST','tab',''); # NOTE: Not yet handled
523
DefKeyVal('LST','showspaces','','true');
524
DefKeyVal('LST','showstringspaces','','true');
525
DefKeyVal('LST','formfeed','');
526
527
#======================================================================
528
# 4.8 Line numbers
529
#======================================================================
530
# Done...
531
DefKeyVal('LST','numbers',''); # none | left | right
532
DefKeyVal('LST','stepnumber','Number');
533
DefKeyVal('LST','numberfirstline','','true');
534
DefKeyVal('LST','numberstyle','');
535
DefKeyVal('LST','numbersep','Dimension');
536
DefKeyVal('LST','numberblanklines','','true');
537
DefKeyVal('LST','firstnumber','');
538
DefKeyVal('LST','name','');
539
NewCounter('lstnumber');
540
DefMacro('\thelstnumber','\arabic{lstnumber}');
541
542
#======================================================================
543
# 4.9 Captions
544
#======================================================================
545
# Done.
546
DefKeyVal('LST','title','');
547
DefKeyVal('LST','caption','');
548
DefKeyVal('LST','label','Semiverbatim');
549
DefKeyVal('LST','nolol','','true'); # Ignorable
550
551
DefPrimitive('\lstlistoflistings',undef);
552
#======================================================================
553
# TDV -- Display "List of Algorithms" as title in contents, and
554
# Use "Algorithm" rather than "Listing" as entity name.
555
#======================================================================
556
#DefMacro('\lstlistlistingname','Listings');
557
#DefMacro('\lstlistingname','Listing');
558
DefMacro('\lstlistlistingname','List of Algorithms');
559
DefMacro('\lstlistingname','Algorithm');
560
#======================================================================
561
DefMacro('\thelstlisting','\arabic{lstlisting}');
562
DefMacro('\thename','');
563
564
DefKeyVal('LST','captionpos',''); # t,b # done
565
DefKeyVal('LST','abovecaptionskip','Dimension'); # Ignorable
566
DefKeyVal('LST','belowcaptionskip','Dimension'); # Ignorable
567
568
#======================================================================
569
# 4.10 Margins and line shape
570
#======================================================================
571
# Ignorable
572
DefKeyVal('LST','linewidth','Dimension');
573
DefKeyVal('LST','xleftmargin','Dimension');
574
DefKeyVal('LST','xrightmargin','Dimension');
575
DefKeyVal('LST','resetmargins','');
576
DefKeyVal('LST','breaklines','','true');
577
DefKeyVal('LST','prebreak','');
578
DefKeyVal('LST','postbreak','');
579
DefKeyVal('LST','breakindent','Dimension');
580
DefKeyVal('LST','breakautoindent','','true');
581
582
#======================================================================
583
# 4.11 Frames
584
#======================================================================
585
# Mosly ignorable, but some could be used
586
DefKeyVal('LST','frame',''); # none | leftline | topline | bottomline | lines | single | shadowbox
587
DefKeyVal('LST','framearound',''); # t|f * 4
588
DefKeyVal('LST','framesep','Dimension');
589
DefKeyVal('LST','rulesep','Dimension');
590
DefKeyVal('LST','framerule','Dimension');
591
DefKeyVal('LST','framexleftmargin','Dimension');
592
DefKeyVal('LST','framexrightmargin','Dimension');
593
DefKeyVal('LST','framextopmargin','Dimension');
594
DefKeyVal('LST','framexbottommargin','Dimension');
595
DefKeyVal('LST','backgroundcolor','');
596
DefKeyVal('LST','rulecolor','');
597
DefKeyVal('LST','fillcolor','');
598
DefKeyVal('LST','rulesepcolor','');
599
600
#======================================================================
601
# 4.12 Indexing
602
#======================================================================
603
DefKeyVal('LST','index','');
604
# HACK: The 2nd optional arg is a list of other classes that should also be indexed!!
605
DefMacro('\lst@@index [Number] [] Until:\end', sub {
606
my($gullet,$n,$c,$words)=@_;
607
my $indexname = lstClassName('index',$n);
608
if($c){
609
my $classes = LookupValue('LST_CLASSES');
610
my @classes = lstSplit($c);
611
map($$classes{$_}{index}=$indexname, @classes); }
612
my $wordslist = LookupValue('LST_WORDS');
613
foreach my $word (keys %$wordslist){
614
delete $$wordslist{$word}{index} if ($$wordslist{$word}{index}||'') eq $indexname; }
615
616
my @words = lstSplit($words);
617
foreach my $word (@words){
618
$$wordslist{$word}{index} = $indexname; }
619
return; });
620
621
DefKeyVal('LST','moreindex','');
622
DefMacro('\lst@@moreindex [Number] [] Until:\end', sub {
623
my($gullet,$n,$c,$words)=@_;
624
my $indexname = lstClassName('index',$n);
625
if($c){
626
my $classes = LookupValue('LST_CLASSES');
627
my @classes = lstSplit($c);
628
map($$classes{$_}{index}=$indexname, @classes); }
629
my $wordslist = LookupValue('LST_WORDS');
630
my @words = lstSplit($words);
631
foreach my $word (@words){
632
$$wordslist{$word}{index} = $indexname; }
633
return; });
634
635
DefKeyVal('LST','deleteindex','');
636
DefMacro('\lst@@deleteindex [Number] [] Until:\end', sub {
637
my($gullet,$n,$c,$words)=@_;
638
my $indexname = lstClassName('index',$n);
639
if($c){
640
my $classes = LookupValue('LST_CLASSES');
641
my @classes = lstSplit($c);
642
foreach my $cl (@classes){
643
delete $$classes{$cl}{index} if ($$classes{$cl}{index}||'') eq $indexname; }}
644
my $wordslist = LookupValue('LST_WORDS');
645
foreach my $word (keys %$wordslist){
646
delete $$wordslist{$word}{index} if ($$wordslist{$word}{index}||'') eq $indexname; }
647
return; });
648
649
DefKeyVal('LST','indexstyle','');
650
DefMacro('\lst@@indexstyle [Number] Until:\end', sub {
651
lstSetClassStyle(lstClassName('index',$_[1]),$_[2]); });
652
653
DefMacro('\lstindexmacro{}','\index{{\ttfamily #1}}');
654
655
#======================================================================
656
# 4.13 Column alignment
657
#======================================================================
658
# Ignorable (?)
659
DefKeyVal('LST','columns','');
660
DefKeyVal('LST','flexiblecolumns','','true');
661
DefKeyVal('LST','keepspaces','','true');
662
#DefKeyVal('LST','basewidth','Dimension'); # or 2 Dimensions!!!!
663
DefKeyVal('LST','basewidth',''); # or 2 Dimensions!!!!
664
DefKeyVal('LST','fontadjust','','true');
665
666
#======================================================================
667
# 4.14 Escaping to LaTeX
668
#======================================================================
669
670
DefKeyVal('LST','texcl','','true');
671
DefMacro('\lst@@texcl Until:\end',sub {
672
my($gullet,$boole)=@_;
673
my $classes = LookupValue('LST_CLASSES');
674
# This only gets comments classes already defined!! Is that correct?
675
my @commentclasses = grep(/^comment/, keys %$classes);
676
if(ToString($boole) eq 'true'){
677
map( $$classes{$_}{eval}=1, @commentclasses); }
678
else {
679
map( delete $$classes{$_}{eval}, @commentclasses); }
680
return; });
681
682
DefKeyVal('LST','mathescape','','true');
683
DefMacro('\lst@@mathescape Until:\end',sub {
684
my($gullet,$boole)=@_;
685
if(ToString($boole) eq 'true'){
686
LookupValue('LST_DELIMITERS')->{'$'} = { open=>'\$', close=>'\$', class=>'mathescape', escape=>1};
687
LookupValue('LST_CLASSES')->{mathescape} = { begin=>T_MATH, end=>T_MATH, eval=>1}; }
688
else {
689
delete(LookupValue('LST_DELIMITERS')->{'$'}); }
690
return; });
691
DefKeyVal('LST','escapechar','');
692
DefMacro('\lst@@escapechar Until:\end',sub {
693
my($gullet,$escape)=@_;
694
$escape = lstDeslash($escape);
695
if($escape){
696
my $escapere = lstRegexp($escape);
697
LookupValue('LST_DELIMITERS')->{$escape} = {open=>$escapere,close=>$escapere,class=>'evaluate',escape=>1};
698
LookupValue('LST_CLASSES')->{evaluate}{eval}=1; }
699
return; });
700
DefKeyVal('LST','escapeinside','');
701
DefMacro('\lst@@escapeinside Until:\end',sub {
702
my($gullet,$escape)=@_;
703
my($escape1,$escape2)=map(lstDeslash($_), $escape->unlist);
704
if($escape1 && $escape2){
705
LookupValue('LST_DELIMITERS')->{$escape1} = { open=>lstRegexp($escape1),close=>lstRegexp($escape2),
706
class=>'evaluate', escape=>1};
707
LookupValue('LST_CLASSES')->{evaluate}{eval}=1; }
708
return; });
709
DefKeyVal('LST','escapebegin','');
710
DefMacro('\lst@@escapebegin Until:\end',sub {
711
LookupValue('LST_CLASSES')->{evaluate}{begin}=$_[1];
712
return; });
713
DefKeyVal('LST','escapeend','');
714
DefMacro('\lst@@escapeend Until:\end',sub {
715
LookupValue('LST_CLASSES')->{evaluate}{end}=$_[1];
716
return; });
717
718
#======================================================================
719
# 4.15 Interface to fancyvrb
720
#======================================================================
721
# NOTE: fancyvrb Not yet handled, probably won't be
722
DefKeyVal('LST','fancyvrb','','true');
723
DefKeyVal('LST','fvcmdparams','');
724
DefKeyVal('LST','morefvcmdparams','');
725
726
#======================================================================
727
# 4.16 Environments
728
#======================================================================
729
DefPrimitive('\lstnewenvironment {}[Number][]{}{}',sub {
730
my($stomach,$name, $n, $opt,$start,$end)=@_;
731
$name = ToString($name);
732
DefMacroI(T_CS("\\begin{$name}"),LaTeXML::Package::convertLaTeXArgs($n,$opt),
733
sub {
734
my($gullet,@args)=@_;
735
$STATE->getStomach->bgroup;
736
Digest(Tokens(LaTeXML::Expandable::substituteTokens($start,@args)));
737
my $text = join('',$gullet->getMouth->readRawLines("\\end{$name}"));
738
$text =~ s/^\s*?\n//s;
739
my @expansion = lstProcessBlock(lstGetTokens('name'),$text);
740
push(@expansion,LaTeXML::Expandable::substituteTokens($end,@args));
741
$STATE->getStomach->egroup;
742
@expansion; });
743
});
744
745
#======================================================================
746
# 4.17 Language definitions
747
#======================================================================
748
749
# \lstdefinelanguage[dialect]{language}[base_dialect]{base_language_if_base_dialect}{keys}[required_aspects]
750
DefMacro('\lstdefinelanguage []{}',
751
'\@ifnextchar[{\@lstdefinelanguage[#1]{#2}}{\@lstdefinelanguage[#1]{#2}[]{}}');
752
Let(T_CS('\lst@definelanguage'),T_CS('\lstdefinelanguage'));
753
754
DefPrimitive('\@lstdefinelanguage []{}[]{} SkipSpaces RequiredKeyVals:LST []',sub {
755
my($stomach,$dialect,$language,$base_dialect,$base_language,$keyvals,$aspects)=@_;
756
my @base=();
757
if($base_language->unlist){
758
push(@base,T_OTHER('['),$base_dialect->unlist,T_OTHER(']')) if $base_dialect;
759
push(@base,$base_language->unlist); }
760
$language = uc(ToString($language)); $language =~ s/\s+//g;
761
my $name = 'LST@LANGUAGE@'.$language;
762
if($dialect && $dialect->unlist){
763
$dialect = uc(ToString($dialect)); $dialect =~ s/\s+//g;
764
$name .= '$'.$dialect; }
765
AssignValue($name
766
=> LaTeXML::KeyVals->new('LST','[',']',(@base ? (language=>Tokens(@base)):()), $keyvals->getPairs)); });
767
768
# Seems to use <language>$<dialect> as the naming scheme.
769
DefPrimitive('\lstalias []{} []{}',sub {
770
my($stomach,$aliasdialect, $alias,$language,$dialect)=@_;
771
# NOTE! Figure out how aliasing is supposed to work...?
772
return; });
773
774
# keywords (keywordstyle in section 4.6)
775
DefKeyVal('LST','keywordprefix',''); # ???
776
DefKeyVal('LST','keywords', 'Semiverbatim');
777
DefMacro('\lst@@keywords [Number] Until:\end', sub {
778
lstSetClassWords(lstClassName('keywords',$_[1]),$_[2]); });
779
DefKeyVal('LST','morekeywords','Semiverbatim');
780
DefMacro('\lst@@morekeywords [Number] Until:\end', sub {
781
lstAddClassWords(lstClassName('keywords',$_[1]),$_[2]); });
782
DefKeyVal('LST','deletekeywords','Semiverbatim');
783
DefMacro('\lst@@deletekeywords [Number] Until:\end', sub {
784
lstDeleteClassWords(lstClassName('keywords',$_[1]),$_[2]); });
785
786
DefKeyVal('LST','ndkeywords','Semiverbatim');
787
DefMacro('\lst@@ndkeywords Until:\end', sub {
788
lstSetClassWords('keywords2',$_[1]); });
789
DefKeyVal('LST','morendkeywords','Semiverbatim');
790
DefMacro('\lst@@morendkeywords Until:\end', sub {
791
lstAddClassWords('keywords2',$_[1]); });
792
DefKeyVal('LST','deletendkeywords','Semiverbatim');
793
DefMacro('\lst@@deletendkeywords Until:\end', sub {
794
lstDeleteClassWords('keywords2',$_[1]); });
795
796
DefKeyVal('LST','texcs','');
797
DefMacro('\lst@@texcs [Number] Until:\end', sub {
798
AssignValue('LST@TEXCS'=>1);
799
lstSetClassWords(lstClassName('texcss',$_[1]),$_[2],"\\"); });
800
DefKeyVal('LST','moretexcs','');
801
DefMacro('\lst@@moretexcs [Number] Until:\end', sub {
802
AssignValue('LST@TEXCS'=>1);
803
lstAddClassWords(lstClassName('texcss',$_[1]),$_[2],"\\"); });
804
DefKeyVal('LST','deletetexcs','');
805
DefMacro('\lst@@deletetexcs [Number] Until:\end', sub {
806
lstDeleteClassWords(lstClassName('texcss',$_[1]),$_[2],"\\"); });
807
808
# directives (directivestyle in section 4.6)
809
DefKeyVal('LST','directives','Semiverbatim');
810
DefMacro('\lst@@directives Until:\end', sub {
811
lstSetClassWords('directives',$_[1]); });
812
DefKeyVal('LST','moredirectives','Semiverbatim');
813
DefMacro('\lst@@moredirectives Until:\end', sub {
814
lstAddClassWords('directives',$_[1]); });
815
DefKeyVal('LST','deletedirectives','Semiverbatim');
816
DefMacro('\lst@@deletedirectives Until:\end', sub {
817
lstDeleteClassWords('directives',$_[1]); });
818
819
DefKeyVal('LST','sensitive','','true');
820
DefKeyVal('LST','alsoletter','');
821
DefMacro('\lst@@alsoletter Until:\end', sub {
822
lstSetCharacterClass('letter',$_[1]); });
823
DefKeyVal('LST','alsodigit','');
824
DefMacro('\lst@@alsodigit Until:\end', sub {
825
lstSetCharacterClass('digit',$_[1]); });
826
DefKeyVal('LST','alsoother','');
827
DefMacro('\lst@@alsoother Until:\end', sub {
828
lstSetCharacterClass('other',$_[1]); });
829
DefKeyVal('LST','otherkeywords',''); # NOTE: Not yet handled
830
831
DefKeyVal('LST','tag','');
832
DefMacro('\lst@@tag OptionalMatch:* OptionalMatch:* [] Until:\end', sub {
833
lstAddDelimiter('delimiter',$_[3],'tagstyle',$_[4],
834
($_[1] ? (recurse=>1):()),
835
($_[2] ? (cummulative=>1):())); });
836
837
# Strings
838
DefKeyVal('LST','string','');
839
DefMacro('\lst@@string [] Until:\end', sub {
840
lstAddDelimiter('string',$_[1],'stringstyle',$_[2]); });
841
DefKeyVal('LST','morestring','');
842
DefMacro('\lst@@morestring [] Until:\end', sub {
843
lstAddDelimiter('string',$_[1],'stringstyle',$_[2]); });
844
DefKeyVal('LST','deletestring','');
845
# How to handle???
846
847
# Comments
848
DefKeyVal('LST','comment','');
849
DefMacro('\lst@@comment [] [] Until:\end', sub {
850
lstAddDelimiter('comment',$_[1],'commentstyle',$_[3]); });
851
DefKeyVal('LST','morecomment','');
852
DefMacro('\lst@@morecomment [] [] Until:\end', sub {
853
lstAddDelimiter('comment',$_[1],'commentstyle',$_[3]); });
854
DefKeyVal('LST','deletecomment','');
855
# How to handle???
856
857
DefKeyVal('LST','keywordcomment','');
858
DefKeyVal('LST','morekeywordcomment','');
859
DefKeyVal('LST','deletekeywordcomment','');
860
DefKeyVal('LST','keywordcommentsemicolon','');
861
DefKeyVal('LST','podcomment','','true');
862
863
DefPrimitive('\lstloadlanguages Semiverbatim', undef);
864
865
#======================================================================
866
# Process the listing
867
# The listing is supplied as a list of strings
868
# The result is a Tokens containing the formatted results
869
sub lstProcess {
870
my($mode,$text)=@_;
871
872
# === Return nothing if print is false
873
return Tokens() unless lstGetBoolean('print');
874
875
# === Possibly strip trailing blank lines.
876
# NOTE: Not sure if this is supposed to trim from the whole listing, or the requested subset(s) of lines!
877
if(!lstGetBoolean('showlines')){ # trim empty lines from end.
878
$text =~ s/\s*$//s; }
879
880
# === Establish line numbering parameters
881
my $name = lstGetLiteral('name');
882
my $firstnumber = lstGetLiteral('firstnumber');
883
my $line0 = (($firstnumber eq 'last')
884
? (LookupValue('LISTINGS_LAST_NUMBER') || 1)
885
: ($firstnumber eq 'auto'
886
? (($name && LookupValue('LISTINGS_LAST_NUMBER_'.$name)) || 1)
887
: $firstnumber));
888
my $numpos = ((lstGetNumber('stepnumber') == 0) ? 'none' : lstGetLiteral('numbers'));
889
AssignValue('LISTINGS_NEEDS_NUMBER'=>(($numpos ne 'none') && lstGetBoolean('numberfirstline')));
890
891
# === Create a line test based on linerange, or firstline & lastline
892
my $linetest = sub { 1; };
893
my($l1,$l2);
894
if(my $lr = lstGetLiteral('linerange')){
895
my @lr = map([split(/-/,$_)], lstSplit($lr));
896
$linetest = sub { grep( ($$_[0] <= $_[0]) && ($_[0] <= $$_[1]), @lr); }; }
897
elsif(($l1 = lstGetNumber('firstline'))
898
&& ($l2 = lstGetNumber('lastline'))){
899
$linetest = sub { ($l1 <= $_[0]) && ($_[0] <= $l2); }; }
900
901
local $LaTeXML::linetest = $linetest;
902
# === These hashes have been set up by "activating" the various keywords.
903
my $words = LookupValue('LST_WORDS');
904
my $delimiters = LookupValue('LST_DELIMITERS');
905
my $classes = LookupValue('LST_CLASSES');
906
my $characters = LookupValue('LST_CHARACTERS');
907
# === Extract some regexps to match various important things
908
my $letter_re = join('', sort keys %{$$characters{letter}});
909
my $digit_re = join('', sort keys %{$$characters{digit}});
910
local $LaTeXML::ID_RE = (LookupValue('LST@TEXCS') ? "\\\\?" : '')."[$letter_re][$letter_re$digit_re]*";
911
912
local $LaTeXML::DELIM_RE = join('|',map($$delimiters{$_}{open}, sort keys %$delimiters));
913
local $LaTeXML::ESCAPE_RE = join('|',map($$delimiters{$_}{open},
914
grep($$delimiters{$_}{escape}, sort keys %$delimiters)));
915
local $LaTeXML::QUOTED_RE = undef;
916
local $LaTeXML::SPACE = (lstGetBoolean('showspaces') ? T_CS('\@lst@visible@space') : T_CS("~"));
917
local $LaTeXML::CASE_SENSITIVE = lstGetBoolean('sensitive');
918
if(!$LaTeXML::CASE_SENSITIVE){ # Clunky, but until know, we don't know
919
foreach my $word (keys %$words){
920
$$words{uc($word)} = $$words{$word}; }}
921
922
# print STDERR "ID_RE : $LaTeXML::ID_RE\n";
923
# print STDERR "ESCAPE_RE : $LaTeXML::ESCAPE_RE\n";
924
# print STDERR "DELIM_RE : $LaTeXML::DELIM_RE\n";
925
# print STDERR "WORDS: ".join(',',map("$_=".join('+',keys %{$$words{$_}}), keys %$words))."\n";
926
# foreach my $cl (keys %$classes){
927
# print STDERR "CLASS: $cl ".join(',',map("$_=".ToString($$classes{$cl}{$_}), keys %{$$classes{$cl}}))."\n"; }
928
# === Start processing
929
# This whole set of vars probably needs to be adjusted,
930
# since we'll need to recognize constructs inside strings that we've already pulled out (strings,comments)
931
# Better would be to treat the whole string.
932
# then gobble lines etc, can probably work...
933
local $LaTeXML::linenum = $line0;
934
local $LaTeXML::colnum = 0;
935
local $LaTeXML::listing = $text;
936
local $LaTeXML::mode = $mode;
937
my @tokens = (T_BEGIN);
938
push(@tokens,lstGetTokens('basicstyle')->unlist);
939
940
while($LaTeXML::listing && ! &$linetest($LaTeXML::linenum)){ # Ignore initial lines?
941
$LaTeXML::listing =~ s/^.*?\n//s;
942
$LaTeXML::linenum++; }
943
if($mode ne 'inline'){
944
push(@tokens,Invocation(T_CS('\setcounter'),T_OTHER('lstnumber'), Number($LaTeXML::linenum)));
945
push(@tokens,Invocation(T_CS('\@lst@startline'),($numpos eq 'left')&& lstDoNumber($LaTeXML::listing=~/^\s*?\n/s)));}
946
push(@tokens,lstProcess_internal());
947
if($mode ne 'inline'){
948
push(@tokens,Invocation(T_CS('\@lst@endline'),($numpos eq 'right')&& lstDoNumber())); }
949
950
# === Save line number for possible later use.
951
AssignValue('LISTINGS_LAST_NUMBER' => CounterValue('lstnumber')->valueOf, 'global');
952
AssignValue('LISTINGS_LAST_NUMBER_'.$name => CounterValue('lstnumber')->valueOf, 'global') if $name;
953
push(@tokens,T_END);
954
# === And finally, return the tokens we've constructed.
955
# print STDERR "LISTING=>".ToString(Tokens(@tokens))."\n";
956
Tokens(@tokens); }
957
958
sub lstProcess_internal {
959
my($end_re)=@_;
960
my $numpos = ((lstGetNumber('stepnumber') == 0) ? 'none' : lstGetLiteral('numbers'));
961
my $words = LookupValue('LST_WORDS');
962
my $delimiters = LookupValue('LST_DELIMITERS');
963
my $classes = LookupValue('LST_CLASSES');
964
my @tokens=();
965
while ($LaTeXML::listing) {
966
# Matched the ending regular expression? (typically a close delimiter)
967
if($end_re && $LaTeXML::listing =~ s/^($end_re)//s){
968
$LaTeXML::colnum += length($1);
969
last; }
970
# Various kinds of delimited expressions: escapes, strings, comments, general delimiters.
971
elsif ($LaTeXML::DELIM_RE && $LaTeXML::listing=~ s/^($LaTeXML::DELIM_RE)//s) {
972
my $open = $1;
973
$LaTeXML::colnum += length($1);
974
my $delim = $$delimiters{$1};
975
my $classname = $$delim{class};
976
push(@tokens,lstClassBegin($classname));
977
# With escapes or texcl, some might be evaluated as TeX; those we match the close delim and simply tokenize.
978
if(lstClassProperty($classname,'eval')){ # If this is a comment with texcl applied, just match & expand
979
$LaTeXML::listing =~ s/^(.*?)($$delim{close})//s; # Simply match until closing regexp
980
my($string,$close)=($1,$2);
981
my @l = split("\n",$string.$close); # This is the only(?) potentially multiline block
982
$LaTeXML::linenum += scalar(@l)-1 if @l > 2; # So adjust line & column
983
push(@tokens,Tokenize($string)); }
984
# Others become tricky because the contents of the string, comment etc may need to be processed
985
# including matching _some_ delimited expressions!
986
# escaped constructs are always matched.
987
# nested : allows comments to be nested (ie the SAME delimiter pair)
988
# recursive: allows any(?) "comments, strings & keywords" to be matched inside.
989
else {
990
local $LaTeXML::DELIM_RE = ($$delim{recursive}
991
? $LaTeXML::DELIM_RE
992
: join('|',grep($_,$LaTeXML::ESCAPE_RE,$$delim{nested} && $$delim{open})));
993
local $LaTeXML::ID_RE = ($$delim{recursive} ? $LaTeXML::ID_RE : undef);
994
local $LaTeXML::QUOTED_RE = join('|',grep($_,$LaTeXML::QUOTED_RE,$$delim{quoted}));
995
local $LaTeXML::SPACE = ($classname && ($classname=~/^string/) && lstGetBoolean('showstringspaces')
996
? T_CS('\@lst@visible@space') : $LaTeXML::SPACE);
997
# Recurse [note that eval should make the individual tokens tokenize as usual!]
998
push(@tokens,lstProcess_internal($$delim{close})); }
999
push(@tokens,lstClassEnd($classname)); }
1000
# Identifiers (possibly keywords, or other classes)
1001
elsif ($LaTeXML::ID_RE && $LaTeXML::listing =~ s/^($LaTeXML::ID_RE)//) {
1002
my $word = $1;
1003
my $lookup = ($LaTeXML::CASE_SENSITIVE ? $word : uc($word));
1004
my $classname = $$words{$lookup}{class};
1005
my @w = Explode($word);
1006
if(my $indexname = $$words{$lookup}{index} || lstClassProperty($classname,'index')){ # Should be indexed?
1007
if(my $index = $indexname && $$classes{$indexname}){
1008
push(@tokens,$$index{begin}->unlist,T_BEGIN,@w,T_END); }}
1009
push(@tokens,lstClassBegin($classname),@w,lstClassEnd($classname)); }
1010
# NOTE: keywordprefix & otherkeywords probably need a specific regexp
1011
# Perhaps a special keywords_re : otherkeywords | keywordprefix$LaTeXML::ID_RE => keyword
1012
1013
# Various kinds of whitespace, newlines, etc.
1014
elsif ($LaTeXML::listing =~ s/^\s*?\n//s) { # Newline
1015
if($LaTeXML::mode ne 'inline'){
1016
push(@tokens,Invocation(T_CS('\@lst@endline'),($numpos eq 'right')&& lstDoNumber($LaTeXML::colnum==1)));
1017
push(@tokens,Invocation(T_CS('\stepcounter'),T_OTHER('lstnumber')));
1018
$LaTeXML::linenum++; # Increment line number
1019
$LaTeXML::colnum = 0; # Reset column number
1020
# NOTE: should ignore blank lines at end of listing, even if they aren't the last line of the code!
1021
# NOTE: should handle showlines, emptylines keywords
1022
while($LaTeXML::listing && ! &$LaTeXML::linetest($LaTeXML::linenum)){ # Ignore next line?
1023
#======================================================================
1024
# TDV
1025
#======================================================================
1026
# $LaTeXML::listing =~ s/^.*?(\n|$)//s;
1027
$LaTeXML::listing =~ s/^.*?(\n)//s;
1028
#======================================================================
1029
push(@tokens,Invocation(T_CS('\stepcounter'),T_OTHER('lstnumber')));
1030
$LaTeXML::linenum++; }
1031
push(@tokens,Invocation(T_CS('\@lst@startline'),
1032
($numpos eq 'left')&& lstDoNumber($LaTeXML::listing=~/^\s*?\n/s))); }
1033
# === Possibly remove $gobble chars from line
1034
my $gobble = lstGetNumber('gobble');
1035
map( $LaTeXML::listing =~ s/^.//, 1..$gobble) if $gobble;
1036
}
1037
elsif ($LaTeXML::listing =~ s/^\t//s) { # Tab expansion
1038
my $tabsize = lstGetNumber('tabsize') || 1;
1039
my $n = ($tabsize-($LaTeXML::colnum % $tabsize));
1040
push(@tokens,map($LaTeXML::SPACE,1..$n));
1041
$LaTeXML::colnum+=$n; }
1042
elsif ($LaTeXML::listing =~ s/^\f//s) { # Formfeed
1043
push(@tokens,lstGetTokens('formfeed')->unlist);
1044
$LaTeXML::colnum++; }
1045
elsif ($LaTeXML::listing =~ s/^\s//s) { # Space (nonbreak)
1046
push(@tokens,$LaTeXML::SPACE);
1047
$LaTeXML::colnum++; }
1048
# Quoted are typically quoted delimiters.
1049
elsif($LaTeXML::QUOTED_RE && $LaTeXML::listing =~ s/^($LaTeXML::QUOTED_RE)//){ # Something quoted.
1050
push(@tokens,T_OTHER($1));
1051
$LaTeXML::colnum += length($1); }
1052
else {
1053
$LaTeXML::listing =~ s/^(.)//s; # Anything else, just pass through.
1054
push(@tokens,T_OTHER($1));
1055
$LaTeXML::colnum++; }
1056
}
1057
@tokens; }
1058
1059
sub lstClassBegin {
1060
my($classname)=@_;
1061
my $class = $classname && LookupValue('LST_CLASSES')->{$classname};
1062
($class
1063
? ( T_BEGIN, ($$class{begin} ? $$class{begin}->unlist : ()), lstClassBegin($$class{class}))
1064
: ()); }
1065
1066
sub lstClassEnd {
1067
my($classname)=@_;
1068
my $class = $classname && LookupValue('LST_CLASSES')->{$classname};
1069
($class
1070
? ( lstClassEnd($$class{class}), ($$class{end} ? $$class{end}->unlist : ()), T_END)
1071
: ()); }
1072
1073
sub lstClassProperty {
1074
my($classname,$property)=@_;
1075
my $class = $classname && LookupValue('LST_CLASSES')->{$classname};
1076
($class && ($$class{$property} ? $$class{$property} : lstClassProperty($$class{class},$property))); }
1077
1078
DefConstructor('\@lst@startline[]',
1079
"<ltx:tr>?#1(<ltx:td class='linenumber'>#1</ltx:td>)<ltx:td><ltx:text>");
1080
DefConstructor('\@lst@endline[]',
1081
"</ltx:text></ltx:td>?#1(<ltx:td class='linenumber'>#1</ltx:td>)</ltx:tr>");
1082
DefConstructor('\@lst@visible@space', "\x{2423}");
1083
1084
sub lstDoNumber {
1085
my($isempty)=@_;
1086
if( (LookupValue('LISTINGS_NEEDS_NUMBER')
1087
|| (($LaTeXML::linenum % lstGetNumber('stepnumber')) == 0))
1088
&& (lstGetBoolean('numberblanklines') || !$isempty) ){
1089
AssignValue('LISTINGS_NEEDS_NUMBER'=>0);
1090
Tokens(T_BEGIN,lstGetTokens('numberstyle')->unlist,T_CS('\thelstnumber'),T_END); }
1091
else {
1092
T_SPACE; }}
1093
# (T_BEGIN,lstGetTokens('numberstyle')->unlist,T_CS("~"),T_END,T_CS("~")); }}
1094
1095
#======================================================================
1096
# Initialize the various parameters...
1097
1098
RawTeX(<<'EoTeX');
1099
\lstset{
1100
alsoletter={abcdefghiklmnopqrstuvwxyzABCDEFGHIKLMNOPQRSTUVWXYZ@$\_},
1101
alsodigit={0123456789},
1102
alsoother={!"#\%&'()*+,-./:;<=>?[\\]^\{|\}~},
1103
float=tbp,floatplacement=tbp,aboveskip=\medskipamount,belowskip=\medskipamount,
1104
lineskip=0pt,boxpos=c,
1105
print=true,firstline=1,lastline=9999999,showlines=false,emptylines=9999999,gobble=0,
1106
style={},language={},printpod=false,usekeywordsintag=true,tagstyle={},
1107
markfirstintag=false,makemacrouse=true,
1108
basicstyle={},identifierstyle={},commentstyle=\itshape,stringstyle={},
1109
keywordstyle=\bfseries,classoffset=0,
1110
emph={},delim={},
1111
extendedchars=false,inputencoding={},upquote=false,tabsize=8,showtabs=false,
1112
tabs={},showspaces=false,showstringspaces=true,formfeed=\bigbreak,
1113
numbers=none,stepnumber=1,numberfirstline=false,numberstyle={},numbersep=10pt,
1114
numberblanklines=true,firstnumber=auto,name={},
1115
title={},caption={},label={},nolol=false,
1116
captionpos=t,abovecaptionskip=\smallskipamount,belowcaptionskip=\smallskipamount,
1117
linewidth=\linewidth,xleftmargin=0pt,xrightmargin=0pt,resetmargins=false,breaklines=false,
1118
prebreak={},postbreak={},breakindent=20pt,breakautoindent=true,
1119
frame=none,frameround=ffff,framesep=3pt,rulesep=2pt,framerule=0.4pt,
1120
framexleftmargin=0pt,framexrightmargin=0pt,framextopmargin=0pt,framexbottommargin=0pt,
1121
backgroundcolor={},rulecolor={},fillcolor={},rulesepcolor={},
1122
frameshape={},
1123
index={},indexstyle=\lstindexmacro,
1124
columns=[c]fixed,flexiblecolumns=false,keepspaces=false,basewidth={0.6em,0.45em},
1125
fontadjust=false,texcl=false,mathescape=false,escapechar={},escapeinside={},
1126
escapebegin={},escapeend={},
1127
fancyvrb=false,fvcmdparams=\overlay1,morefvcmdparams={},
1128
ndkeywordstyle=keywordstyle,texcsstyle=keywordstyle,directivestyle=keywordstyle
1129
}
1130
EoTeX
1131
1132
#======================================================================
1133
# Something like this ought to be built in?
1134
sub readRawConfigFile {
1135
my($file)=@_;
1136
my $path = FindFile($file);
1137
$path = `kpsewhich $file` unless $path;
1138
chomp($path);
1139
if($path){
1140
my $stomach = $STATE->getStomach;
1141
my $gullet = $stomach->getGullet;
1142
my $cmts = LookupValue('INCLUDE_COMMENTS');
1143
AssignValue('INCLUDE_COMMENTS'=>0);
1144
$gullet->openMouth(LaTeXML::StyleMouth->new($path),1);
1145
my $mouth = $gullet->getMouth;
1146
my $token;
1147
while($token = $gullet->readXToken(0)){
1148
next if $token->equals(T_SPACE);
1149
$stomach->invokeToken($token); }
1150
$gullet->closeMouth if $mouth eq $gullet->getMouth; # may already close from \endinput!
1151
AssignValue('INCLUDE_COMMENTS'=>$cmts); }
1152
else {
1153
Info("expected:$file Couldn't find config file $file"); }}
1154
1155
# Finally, we want to load the definitions from the configurations...
1156
# Actually, we should just load .cfg
1157
# and the extra files should be loaded as needed, but...
1158
sub lstLoadConfiguration {
1159
readRawConfigFile("listings.cfg");
1160
# And NOW read in the language definitions.
1161
foreach my $file (lstSplit(Digest(T_CS('\lstlanguagefiles')))){
1162
readRawConfigFile($file); }}
1163
1164
lstLoadConfiguration();
1165
1166
#**********************************************************************
1167
1;
1168
1169