]> git.lyx.org Git - lyx.git/blob - lib/reLyX/ReadCommands.pm
complie fix
[lyx.git] / lib / reLyX / ReadCommands.pm
1 # This file is part of reLyX
2 # Copyright (c) 1998-9 Amir Karger karger@post.harvard.edu
3 # You are free to use and modify this code under the terms of
4 # the GNU General Public Licence version 2 or later.
5
6 package ReadCommands;
7 # Read a file containing LaTeX commands and their syntax
8 # Also, read a file containing LyX layouts and their LaTeX equivalents
9
10 use strict; 
11
12 # Variables needed by other modules
13 # ToLayout is a ref to a hash which contains LyX layouts & LaTeX equivalents
14 # regular_env is a list of environments that have "reLyXable" LaTeX in them
15 # math_trans is a hash of math commands and what they're translated to
16 use vars qw($ToLayout @regular_env %math_trans);
17
18 #    CommandHash is the hash of LaTeX commands and their syntaxes which
19 # this package builds.
20 my %CommandHash;
21
22 # Name of the environment containing names of regular environments :)
23 my $regenv_name = "reLyXre";
24 # Name of environment containing translations of math commands
25 my $math_trans_name = "reLyXmt";
26 # Variables set when in the above environments
27 my ($in_regular_env, $in_math_trans);
28 my @Environments = qw($regenv_name $math_trans_name);
29
30 # This word in a command's argument (in the syntax file) means that reLyX
31 # should translate that argument as regular LaTeX
32 my $Translate_Word = "translate";
33
34 #########################  READ COMMAND SYNTAX  ################################
35 sub read_syntax_files {
36 # This subroutine calls the TeX parser & translator to read LaTeX syntax file(s)
37 #    It sets the list of "regular" environments, environments which aren't known
38 # by reLyX, but which contain text that reLyX can translate.
39 #    It also reads math commands which should be translated (e.g., \sp -> ^)
40 #
41 # @_ contains the syntax file(s) to read
42
43     my @syntaxfiles = @_;
44
45 # Before anything else, set the package-wide variables based on the
46 #    user-given flags
47     # opt_d is set to 1 if '-d' option given, else (probably) undefined
48     my $debug_on = (defined($main::opt_d) && $main::opt_d);
49
50     # opt_r is a group of environments to copy like regular text
51     # If opt_r wasn't given, then there are no such special environments
52     @regular_env = (defined $main::opt_r ? (split(/,/, $main::opt_r)) : () );
53
54     # The only important commands to pass are \begin and \end, so that the
55     # parser realizes they start/end environments, as opposed to being
56     # regular old tokens.
57     my %MyTokens = ( '{' => $Text::TeX::Tokens{'{'},
58                      '}' => $Text::TeX::Tokens{'}'},
59                      '\begin' => $Text::TeX::Tokens{'\begin'},
60                      '\end' => $Text::TeX::Tokens{'\end'},
61     );
62     
63     my $InFileName;
64     foreach $InFileName (@syntaxfiles) {
65         die "could not find syntax file $InFileName" unless -e $InFileName;
66         my $zzz=$debug_on ? "from $InFileName " :"";
67         warn "Reading LaTeX command syntax $zzz\n";
68
69         # Open the file to turn into LyX.
70         my $infile = new Text::TeX::OpenFile $InFileName,
71             'defaultact' => \&read_commands,
72             'tokens' => \%MyTokens;
73
74         # When we start (each file), we're not reading regular environments yet
75         $in_regular_env = 0;
76
77         # Process the file
78         $infile->process;
79     }
80
81     if ($debug_on) {
82         print "Regular environments: @regular_env\n";
83         my @mathkeys = keys(%math_trans);
84         print "     Math command     |  translation\n" if @mathkeys;
85         foreach (@mathkeys) { printf("%20s       %s\n",$_,$math_trans{$_}) }
86     }
87
88     #warn "Done reading commands\n";
89     return;
90 } # end subroutine call_parser
91
92 sub read_commands {
93 # This subroutine is called by Text::TeX::process
94 # Arg0 is the token we just ate
95 # Arg1 is the file object we're reading from
96 #
97 #    We create a hash, where each command is a key. The value is just a string
98 # of zero or more 'o' and 'r' characters. Each 'o' stands for an optional
99 # argument, 'r' stands for a required argument. 'R' stands for a required
100 # argument whose text will be regular LaTeX, e.g., the argument to \mbox{}
101 #    In addition, the $regenv_name environment contains
102 # regular environments, like those input with the -r option.
103 #    Note that if a command is found more than once, then it wil be overwritten.
104 # This is a feature. This way, a user-defined syntax file can overwrite the
105 # argument list found in the default syntax file.
106     my ($token,$fileobject) = (shift,shift);
107
108     my $type = ref($token);
109     $type =~ s/^Text::TeX::// or die "unknown token type $type from Text::TeX";
110     #print $token->exact_print, unless $type eq "Paragraph";
111     #print $token->comment if $token->comment;
112
113     # Because there's no token list, ALL tokens will be
114     #    Paragraph, Text, or Token
115     SWITCH: for ($type) {
116        # Handle blank lines.
117         if (/Paragraph/) {
118             # don't do anything
119             last SWITCH;
120
121         } elsif (/^Token/) {
122             # Comment in its own paragraph... skip
123             last SWITCH unless defined($token->print);
124
125             if ($in_math_trans) { # read translations of math commands
126                 my $key = $token->print;
127                 # Translation is whatever's in the argument to the token
128                 # (There might be multiple tokens in there)
129                 my @vals = $fileobject->eatBalanced->contents;
130                 my $val = join ("", map {$_->exact_print} @vals);
131                 $math_trans{$key} = $val;
132             
133             } else { # regular portion of syntax file
134                 my ($dum2);
135                 my $args = "";
136                 # read while there are arguments
137                 while (($dum2 = $fileobject->lookAheadToken) &&
138                        ($dum2 =~ /^[[{]$/)) {
139                     if ($dum2 eq '[') { #eat optional argument - assumed simple
140                         $fileobject->eatOptionalArgument;
141                         $args .= "o";
142                     } else {
143                         my $tok = $fileobject->eatBalanced or warn "bad group";
144                         if ($tok->exact_print eq $Translate_Word) {
145                             $args .= "R";
146                         } else {
147                             $args .= "r";
148                         }
149                     } # end if $dummy = [{
150                 } # done reading command
151                 $CommandHash{$token->print} = $args;
152             } # in math trans env or regular token?
153
154             last SWITCH;
155
156         } elsif (/^Begin::Group::Args/) {
157             my $env = $token->environment;
158             CASE: {
159                 $in_regular_env = 1, last CASE if $env eq $regenv_name;
160                 $in_math_trans = 1,  last CASE if $env eq $math_trans_name;
161                 warn "Unknown environment $env in syntax file";
162             }
163
164         } elsif (/^End::Group::Args/) {
165             my $env = $token->environment;
166             CASE: {
167                 $in_regular_env = 0, last CASE if $env eq $regenv_name;
168                 $in_math_trans = 0,  last CASE if $env eq $math_trans_name;
169                 warn "Unknown environment $env in syntax file";
170             }
171
172         } elsif (/^Text/) {
173             # don't do anything unless we're reading environments
174             if ($in_regular_env) {
175                 my @new_envs = (split(/\s+/, $token->print));
176                 @new_envs = grep ($_, @new_envs); # remove empty elements
177                 push @regular_env,@new_envs;
178             }
179             last SWITCH;
180         } else {
181             die "unexpected token type $type";
182         }
183
184     } # end SWITCH
185
186 } # end sub read_commands
187
188 sub Merge {
189 #    This sub creates a token list (which could be used to call a Text::TeX
190 # parser) from %CommandHash, and merges it with the input token list
191 #    If a command takes any required arguments, it will be a report_args,
192 # but if it just takes an optional argument, it can stay a regular old token.
193 # In either case, we insert a new field, "relyx_args", into the token list,
194 # which is the expected order of arguments for that command. Even if there
195 # are no args, we insert an empty relyx_args, so that we can differentiate
196 # between a truly unknown token and a known token which takes no args.
197 #
198 # We don't allow a new command to override a command that already exists in
199 # OldHash, i.e., one that was defined explicitly in the calling sub.
200 #
201 # Arg0 is a (reference to an) existing token list
202     my $OldHashRef = shift;
203
204     foreach (keys %CommandHash) {
205         my $val = $CommandHash{$_};
206         my ($entry, $count, @foo);
207         if (!exists $OldHashRef->{$_}) {
208             if ($count = scalar(@foo = ($val =~ /r/gi))) {
209                 # TeX.pm will make this a TT::BegArgsToken and $count-1
210                 #    TT::ArgTokens, followed by a TT::EndArgsToken
211                 $OldHashRef->{$_} = {"Type" => 'report_args',
212                                      "count" => $count,
213                                      "relyx_args" => $val};
214             } else { # only non-required args
215                 # Make it a regular TT::Token, but write relyx_args
216                 #    (even if $val is "")
217                 $OldHashRef->{$_} = {"relyx_args" => $val};
218             }
219         }
220     } # end foreach
221
222 } # end sub Merge
223
224 ############################  READ LAYOUTS  ####################################
225 sub read_layout_files {
226 # This subroutine reads a textclass-specific layout file and all files
227 # included in that file.
228 #    It sets up the layout hash table. For each environment, it describes which
229 # layout that environment refers to. It does the same for macros which
230 # begin LyX layouts (e.g., \section)
231 #    If we read a command that's not already in CommandHash, it means that this
232 # layout has some commands that aren't in syntax.default. If so, we ASSUME
233 # that the command takes just one required argument, and put it in
234 # CommandHash, so that &Merge will eventually put these commands into the
235 # token lists.
236 #
237 # TODO: we actually need to allow more sophisticated stuff. E.g. \foilhead
238 # is converted to Foilhead or ShortFoilHead (foils.layout) depending on whether
239 # the command has "r" or "or" arguments. Reading LatexParam (if it exists)
240 # can help us with this.
241 # Default is "r". Just unshift other args as you read them, since latexparam
242 # is put in between macro & the argument
243 # TODO: We need to store ToLayout s.t. we can have > 1 layout per command.
244 # Maybe by default just have one layout, but if (ref(layout)) then read
245 # more args & thereby figure out which layout?
246
247 # Arg0 is the name of the documentclass
248     use FileHandle;
249     use File::Basename;
250     my $doc_class = shift;
251     my @filestack;
252     my $fh;
253     my $line;
254     # ToLayout{latexname} stores layout; so ReversHash{layout} = latexname
255     my %ReverseHash;
256     my $debug_on = (defined($main::opt_d) && $main::opt_d);
257
258     # look for layout file in $HOME/.lyx first, then system layouts directory
259     my $searchname = "$doc_class.layout";
260     my @searchdirs = ();
261     my $personal_layout = "$main::dot_lyxdir/layouts";
262     push(@searchdirs,$personal_layout) if -e $personal_layout;
263     my $system_layout = "$main::lyxdir/layouts";
264     # I guess this won't exist if running reLyX without installing...
265     # Of course, in that case, this will probably break
266     push(@searchdirs,$system_layout) if -e $system_layout;
267     my @foundfiles = grep(-e "$_/$searchname", @searchdirs) or
268           die "Cannot find layout file $searchname in dir(s) @searchdirs";
269     my $LayoutFileName = "$foundfiles[0]/$searchname"; # take first one we found
270
271     $fh = new FileHandle;
272     $fh->open ("<$LayoutFileName");
273     my $zzz=$debug_on ? "$LayoutFileName" :"";
274     warn "Reading layout file $zzz\n";
275     push @filestack, $fh;
276
277     # Read the layout file!
278     my ($lyxname, $latexname, $latextype, $latexparam, $keepempty);
279     my $fname;
280     while() {
281         # Read a line. If eof, pop the filestack to return to the file
282         #    that included this file *or* finish if the stack's empty
283         unless (defined ($line = <$fh>)) {
284             $fh->close;
285             pop @filestack;
286             last unless ($#filestack+1); # finish when stack is empty
287             $fh = $filestack[-1];
288             next; # read another line from the "calling" file
289         }
290
291         # Skip blank lines
292         next if $line =~ /^\s*$/;
293
294         # Split the line. Use limit 2 since there may be whitespace in 2nd term
295         my ($field_name, $field_stuff) = split(' ', $line, 2);
296         $field_name = lc($field_name); # LyX is case insensitive for fields
297         if (defined($field_stuff)) {
298             $field_stuff =~ s/^\"(.*)\"/$1/;
299             chomp ($field_stuff);
300             # Since split is limited to 2 fields, there may be extra whitespace
301             # at end. LyX breaks on a "\layout Abstract " command!
302             $field_stuff =~ s/\s*$//;
303         }
304
305         # This set of ifs deals with lines outside a style definition
306         if ($field_name eq "style") { # start a style definition
307             $lyxname = $field_stuff;
308             # Styles in LyX have spaces, but _ in layout files
309             $lyxname =~ s/_/ /g;
310             $latexname  = ""; # make sure these variables are unset
311             $latextype  = "";
312             $latexparam = "";
313             $keepempty = 0;
314         } elsif ($field_name eq "input") { #include a file
315             $searchname = $field_stuff;
316             @foundfiles = grep(-e "$_/$searchname", @searchdirs) or
317               die "Cannot find layout file $searchname in dir(s) @searchdirs";
318             $fname = "$foundfiles[0]/$searchname"; # take first one we found
319             $fh = new FileHandle;
320             push @filestack, $fh;
321             $fh->open("<$fname");
322             print "Reading included layout file $fname\n" if $debug_on;
323         }
324
325         next unless $lyxname; # not w/in style definition
326
327         # This set of ifs deals with lines within a Style definition
328         if ($field_name eq "latexname") {
329             $latexname = $field_stuff;
330             next;
331         } elsif ($field_name eq "latexparam") {
332             #$dum = $field_stuff;
333             $latexparam = $field_stuff;
334             next;
335         } elsif ($field_name eq "latextype") {
336             $latextype = $field_stuff;
337             next;
338         } elsif ($field_name eq "keepempty") {
339             $keepempty = $field_stuff;
340             next;
341         } elsif ($field_name eq "copystyle") { # copy an existing style
342             # "if" is necessary in case someone tries "CopyStyle Standard"
343             if (exists $ReverseHash{$field_stuff}) {
344                 my $layref = $ReverseHash{$field_stuff};
345                 $latexname  = $layref->{"name"};
346                 $latextype  = $layref->{"type"};
347                 $latexparam = $layref->{"param"};
348                 $keepempty = $layref->{"keepempty"};
349             }
350
351         # When you get to the end of a definition, create hash table entries
352         #    (if you've found the right information)
353         } elsif ($field_name eq "end") {
354
355             if ($latextype and $latexname) {
356                 # Create reverse hash entry (needed for CopyStyle)
357                 # Do it before making modifications to $latexname, e.g.
358                 $ReverseHash{$lyxname} = {"name"  => $latexname,
359                                           "type"  => $latextype,
360                                           "param" => $latexparam,
361                                           "keepempty" => $keepempty,
362                                           };
363
364                 my ($nest, $skip) = (0,0);
365                 for ($latextype) { # make $_=$latextype
366                     if (/^Command/) {
367                         # Macros need a '\' before them. Environments don't
368                         $latexname = '\\' . $latexname;
369
370                         # Create the command if it wasn't in syntax.default
371                         unless (exists $CommandHash{$latexname}) {
372                             $CommandHash{$latexname} = "r";
373                         }
374
375                     } elsif (/^Environment/) {
376                         $nest = 1;
377                     } elsif (/Item_Environment/i || /List_Environment/) {
378                         $nest = 1;
379
380                     # layout Standard has LatexType Paragraph. It shouldn't
381                     #    have any hash entry at all
382                     } elsif (/^Paragraph$/) {
383                         $skip = 1;
384                     } else {
385                         warn "unknown LatexType $latextype" . 
386                              "for $latexname (layout $lyxname)!\n";
387                     }
388             # Handle latexparam, if any
389 #           if ($dum =~ s/^"(.*)"/$1/) { # newer layout file syntax
390 #               while ($dum =~ /^[[{]/) {
391 #                   $dum =~ s/\[.*?\]// && ($latexargs = "o$latexargs") or
392 #                   $dum =~ s/\{.*?\}// && ($latexargs = "r$latexargs");
393 #               }
394 #               warn "leftover LatexParam stuff $dum" if $dum;
395 #           } else { # 0.12.0
396 #               if ($latextype eq "Command") {optarg}
397 #               else {req. arg}
398 #           }
399                 } #end for
400
401                 # Create the hash entry
402                 unless ($skip) {
403                     $ToLayout->{$latexname} = {"layout" => $lyxname,
404                                                 "nestable" => $nest,
405                                                 "keepempty" => $keepempty};
406                 }
407
408                 # Now that we've finished the style, unset $lyxname so that
409                 #     we'll skip lines until the next style definition
410                 $lyxname = "";
411             } # end if ($latextype and $latexname)
412         } # end if on $line
413             
414     } #end while
415
416 ## Print every known layout
417 #    print "     LatexName            Layout        Keepempty?\n";
418 #    foreach (sort keys %$ToLayout) {
419 #        printf "%20s%15s     %1d\n",$_,$ToLayout->{$_}{'layout'},
420 #             $ToLayout->{$_}{'keepempty'};
421 #    };
422     #warn "Done reading layout files\n";
423     return;
424 } # end sub read_layout_files
425
426
427
428 1; # return TRUE to calling routine