summaryrefslogtreecommitdiff
path: root/source/luametatex/source/tex/texfileio.c
blob: 4f712401b37b5c1daca967fe033cc9311489db62 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
/*
    See license.txt in the root of this project.
*/

# include "luametatex.h"

fileio_state_info lmt_fileio_state = {
   .io_buffer        = NULL,
   .io_buffer_data   = {
        .minimum   = min_buffer_size,
        .maximum   = max_buffer_size,
        .size      = siz_buffer_size,
        .step      = stp_buffer_size,
        .allocated = 0,
        .itemsize  = sizeof(unsigned char),
        .top       = 0,
        .ptr       = 0,
        .initial   = memory_data_unset,
        .offset    = 0,
   },
   .io_first         = 0,
   .io_last          = 0,
   .name_in_progress = 0,
   .log_opened       = 0,
   .job_name         = NULL,
   .log_name         = NULL,
   .fmt_name         = NULL
};

/*tex

    Once \TEX\ is working, you should be able to diagnose most errors with the |\show| commands and
    other diagnostic features. Because we have made some internal changes the optional debug interface
    has been removed.

*/

# define reserved_io_buffer_slots 256

void tex_initialize_fileio_state(void)
{
    int size = lmt_fileio_state.io_buffer_data.minimum;
    lmt_fileio_state.io_buffer = aux_allocate_clear_array(sizeof(unsigned char), size, reserved_io_buffer_slots);
    if (lmt_fileio_state.io_buffer) {
        lmt_fileio_state.io_buffer_data.allocated = size;
    } else {
        tex_overflow_error("buffer", size);
    }
}

int tex_room_in_buffer(int top)
{
    /*tex Beware: |top| can exceed the old size plus the step. */
    if (top > lmt_fileio_state.io_buffer_data.top) {
       lmt_fileio_state.io_buffer_data.top = top;
        if (top > lmt_fileio_state.io_buffer_data.allocated) {
            unsigned char *tmp = NULL;
            if (top <= lmt_fileio_state.io_buffer_data.size) {
                if (lmt_fileio_state.io_buffer_data.allocated + lmt_fileio_state.io_buffer_data.step > top) {
                    top = lmt_fileio_state.io_buffer_data.allocated + lmt_fileio_state.io_buffer_data.step;
                    if (top > lmt_fileio_state.io_buffer_data.size) {
                        top = lmt_fileio_state.io_buffer_data.size;
                    }
                }
                if (top > lmt_fileio_state.io_buffer_data.allocated) {
                    lmt_fileio_state.io_buffer_data.allocated = top;
                    tmp = aux_reallocate_array(lmt_fileio_state.io_buffer, sizeof(unsigned char), top, reserved_io_buffer_slots);
                    lmt_fileio_state.io_buffer = tmp;
                }
            }
            lmt_run_memory_callback("buffer", tmp ? 1 : 0);
            if (! tmp) {
                tex_overflow_error("buffer", top);
                return 0;
            }
        }
    }
    return 1;
}

static int tex_aux_open_outfile(FILE **f, const char *name, const char *mode)
{
    FILE *res = aux_utf8_fopen(name, mode);
    if (res) {
        *f = res;
        return 1;
    }
    return 0;
}

/*tex

    We conform to the way \WEBC\ does handle trailing tabs and spaces. This decade old behaviour
    was changed in September 2017 and can introduce compatibility issues in existing workflows.
    Because we don't want too many differences with upstream \TEX live we just follow up on that
    patch and it's up to macro packages to deal with possible issues (which can be done via the
    usual callbacks. One can wonder why we then still prune spaces but we leave that to the reader.

    Patched original comment:

    Make last be one past the last non-space character in \quote {buffer}, ignoring line
    terminators (but not, e.g., tabs). This is because we are supposed to treat this like a line of
    TeX input. Although there are pathological cases (|SP CR SC CR|) where this differs from
    input_line below, and from previous behavior of removing all whitespace, the simplicity of
    removing all trailing line terminators seems more in keeping with actual command line
    processing.

    The |IS_SPC_OR_EOL| macro deals with space characters (|SPACE 32|) and newlines (|CR| and |LF|)
    and no longer looks at tabs (|TAB 9|).

*/

/*
    The terminal input code is gone as is the read related code (that had already been nicely
    cleaned up and abstracted but that is the price we pay for stepwise progress. That code is
    still in the git repository of course.

    At some point I might do the same as we do in mplib: four callbacks for open, close, read
    and write (in which case the log goes via write). Part of the management is them moved to
    \LUA\ and we save a lookup.

    When I adapted the code in this module and the one dealing with errors, I decided to delegate
    all interaction to \LUA, also because the sometimes tight integration in the scanning and
    expansion mechanisms. In the 2021 TeX tuneup there have been some patches in the interaction
    code and some remarks ring a bell: especially the relation between offering feedback and
    waiting for input. However, because we delegate to \LUA, the engine is no longer responsible
    for what the macro package lets the user do in case of an error. For instance, in \CONTEXT\ we
    just abort the run: it makes no sense to carry on the wrong way. Computers are fast enough for
    a \quotation {Fix and run again.} approach. But we do offer the message and optional help as
    cue. On the agenda is a further abstraction of error handling. This deviation is fine as we
    obey Don's wish to not call it \TEX\ but instead add some more letters to the name.

*/

int tex_lua_a_open_in(const char *fn)
{
    int callback_id = lmt_callback_defined(open_data_file_callback);
    if (callback_id > 0) {
        int k = lmt_run_and_save_callback(lmt_lua_state.lua_instance, callback_id, "S->", fn);
        lmt_input_state.in_stack[lmt_input_state.cur_input.index].input_file_callback_id = k;
        return k > 0;
    } else {
        tex_emergency_message("startup error", "missing open_data_file callback");
        tex_emergency_exit();
        return 0;
    }
}

void tex_lua_a_close_in()
{
    int k = lmt_input_state.in_stack[lmt_input_state.cur_input.index].input_file_callback_id;
    if (k > 0) {
        lmt_run_saved_callback_close(lmt_lua_state.lua_instance, k);
        lmt_destroy_saved_callback(lmt_lua_state.lua_instance, k);
        lmt_input_state.in_stack[lmt_input_state.cur_input.index].input_file_callback_id = 0;
    }
}

/*tex

    Binary input and output are done with \CCODE's ordinary procedures, so we don't have to make
    any other special arrangements for binary \IO. Text output is also easy to do with standard
    routines. The treatment of text input is more difficult, however, because of the necessary
    translation to |unsigned char| values. \TEX's conventions should be efficient, and they should
    blend nicely with the user's operating environment.

    Input from text files is read one line at a time, using a routine called |lua_input_ln|. This
    function is defined in terms of global variables called |buffer|, |first|, and |last| that will
    be described in detail later; for now, it suffices for us to know that |buffer| is an array of
    |unsigned char| values, and that |first| and |last| are indices into this array representing
    the beginning and ending of a line of text.

    The lines of characters being read: |buffer|, the first unused position in |first|, the end of
    the line just input |last|, the largest index used in |buffer|: |max_buf_stack|.

    The |lua_input_ln| function brings the next line of input from the specified file into available
    positions of the buffer array and returns the value |true|, unless the file has already been
    entirely read, in which case it returns |false| and sets |last:=first|. In general, the
    |unsigned char| numbers that represent the next line of the file are input into |buffer[first]|,
    |buffer[first + 1]|, \dots, |buffer[last - 1]|; and the global variable |last| is set equal to
    |first| plus the length of the line. Trailing blanks are removed from the line; thus, either
    |last = first| (in which case the line was entirely blank) or |buffer[last - 1] <> " "|.

    An overflow error is given, however, if the normal actions of |lua_input_ln| would make |last
    >= buf_size|; this is done so that other parts of \TEX\ can safely look at the contents of
    |buffer[last+1]| without overstepping the bounds of the |buffer| array. Upon entry to
    |lua_input_ln|, the condition |first < buf_size| will always hold, so that there is always room
    for an \quote {empty} line.

    The variable |max_buf_stack|, which is used to keep track of how large the |buf_size| parameter
    must be to accommodate the present job, is also kept up to date by |lua_input_ln|.

    If the |bypass_eoln| parameter is |true|, |lua_input_ln| will do a |get| before looking at the
    first character of the line; this skips over an |eoln| that was in |f^|. The procedure does not
    do a |get| when it reaches the end of the line; therefore it can be used to acquire input from
    the user's terminal as well as from ordinary text files.

    Since the inner loop of |lua_input_ln| is part of \TEX's \quote {inner loop} --- each character
    of input comes in at this place --- it is wise to reduce system overhead by making use of
    special routines that read in an entire array of characters at once, if such routines are
    available.

*/

int tex_lua_input_ln(void) /*tex |bypass_eoln| was not used */
{
    int callback_id = lmt_input_state.in_stack[lmt_input_state.cur_input.index].input_file_callback_id;
    if (callback_id > 0) {
        lua_State *L = lmt_lua_state.lua_instance;
        int last_ptr = 0;
        lmt_fileio_state.io_last = lmt_fileio_state.io_first;
        last_ptr = lmt_run_saved_callback_line(L, callback_id, lmt_fileio_state.io_first);
        if (last_ptr < 0) {
            return 0;
        } else if (last_ptr > 0) {
            lmt_fileio_state.io_last = last_ptr;
            if (last_ptr > lmt_fileio_state.io_buffer_data.top) {
                lmt_fileio_state.io_buffer_data.top = last_ptr;
            }
        }
        return 1;
    } else {
        return 0;
    }
}

/*tex

    We need a special routine to read the first line of \TEX\ input from the user's terminal.
    This line is different because it is read before we have opened the transcript file; there is
    sort of a \quote {chicken and egg} problem here. If the user types |\input paper| on the first
    line, or if some macro invoked by that line does such an |\input|, the transcript file will be
    named |paper.log|; but if no |\input| commands are performed during the first line of terminal
    input, the transcript file will acquire its default name |texput.log|. (The transcript file
    will not contain error messages generated by the first line before the first |\input| command.)

    The first line is special also because it may be read before \TEX\ has input a format file. In
    such cases, normal error messages cannot yet be given. The following code uses concepts that
    will be explained later.

    Different systems have different ways to get started. But regardless of what conventions are
    adopted, the routine that initializes the terminal should satisfy the following specifications:

    \startitemize[n]

        \startitem
            It should open file |term_in| for input from the terminal.
        \stopitem

        \startitem
            If the user has given a command line, this line should be considered the first line of
            terminal input. Otherwise the user should be prompted with |**|, and the first line of
            input should be whatever is typed in response.
        \stopitem

        \startitem
            The first line of input, which might or might not be a command line, should appear in
            locations |first| to |last-1| of the |buffer| array.
        \stopitem

        \startitem
            The global variable |loc| should be set so that the character to be read next by \TEX\
            is in |buffer[loc]|. This character should not be blank, and we should have |loc < last|.
        \stopitem

    \stopitemize

    It may be necessary to prompt the user several times before a non-blank line comes in. The
    prompt is |**| instead of the later |*| because the meaning is slightly different: |\input|
    need not be typed immediately after |**|.)

    The following code does the required initialization. If anything has been specified on the
    command line, then |t_open_in| will return with |last > first|.

    This code has been adapted and we no longer ask for a name. It makes no sense because one needs
    to initialize the primitives and backend anyway and no one is going to do that interactively.
    Of course one can implement a session in \LUA. We keep the \TEX\ trick to push the name into
    the input buffer and then exercise an |\input| which ensures proper housekeeping. There is a
    bit overkill in the next function but for now we keep it (as reference).

    For a while copying the argument to th ebuffer lived in the engine lib but it made no sense
    to duplicate code, so now it's here. Anyway, the following does no longer apply:

    \startquotation
    This is supposed to open the terminal for input, but what we really do is copy command line
    arguments into \TEX's buffer, so it can handle them. If nothing is available, or we've been
    called already (and hence, |argc == 0|), we return with |last = first|.
    \stopquotation

    In \LUAMETATEX\ we don't really have a terminal. In the \LUATEX\ precursor we used to append
    all the remaining arguments but now we just take the first one. If one wants filenames with
    spaces \unknown\ use quotes. Keep in mind that original \TEX\ permits this:

    \starttyping
    tex ... filename \\hbox{!} \\end
    \stoptyping

    But we don't follow that route in the situation where \LUA\ is mostly in charge of passing
    input from files and the console.

    In the end I went for an easier solution: just pass the name to the file reader. But we keep
    this as nostalgic reference to how \TEX\ originally kin dof did these things.

    \starttyping
    int input_file_name_pushed(void)
    {
        const char *ptr = engine_input_filename();
        if (ptr) {
            int len = strlen(ptr);
            fileio_state.io_buffer[fileio_state.io_first] = 0;
            if (len > 0 && room_in_buffer(len + 1)) {
                // We cannot use strcat, because we have multibyte UTF-8 input. Hm, why not.
                fileio_state.io_last= fileio_state.io_first;
                while (*ptr) {
                    fileio_state.io_buffer[fileio_state.io_last++] = (unsigned char) * (ptr++);
                }
                // Backtrack over spaces and newlines.
                for (
                    --fileio_state.io_last;
                    fileio_state.io_last >= fileio_state.io_first && IS_SPC_OR_EOL(fileio_state.io_buffer[fileio_state.io_last]);
                    --fileio_state.io_last
                );
                // Terminate the string.
                fileio_state.io_buffer[++fileio_state.io_last] = 0;
                // One more time, this time converting to \TEX's internal character representation.
                if (fileio_state.io_last > fileio_state.io_first) {
                    input_state.cur_input.loc = fileio_state.io_first;
                    while ((input_state.cur_input.loc < fileio_state.io_last) && (fileio_state.io_buffer[input_state.cur_input.loc] == ' ')) {
                        ++input_state.cur_input.loc;
                    }
                    if (input_state.cur_input.loc < fileio_state.io_last) {
                        input_state.cur_input.limit = fileio_state.io_last;
                        fileio_state.io_first = fileio_state.io_last + 1;
                    }
                    if (input_state.cur_input.loc < input_state.cur_input.limit) {
                        return 1;
                    }
                }
            }
        }
        fileio_state.io_first = 1;
        fileio_state.io_last = 1;
        return 0;
    }
    \stopttyping

    It's this kind of magic that can take lots of time to play with and figure out, also because
    we cannot break expectations too much.

*/

/*tex

    Per June 22 2020 the terminal code is gone. See |texlegacy.c| for the old, already adapted
    long ago, code. It was already shedulded for removal a while. We only keep the update.

*/

void tex_terminal_update(void) /* renamed, else conflict in |lmplib|. */
{
    fflush(stdout);
}

/*tex

    It's time now to fret about file names. Besides the fact that different operating systems treat
    files in different ways, we must cope with the fact that completely different naming conventions
    are used by different groups of people. The following programs show what is required for one
    particular operating system; similar routines for other systems are not difficult to devise.

    \TEX\ assumes that a file name has three parts: the name proper; its \quote {extension}; and a
    \quote {file area} where it is found in an external file system. The extension of an input file
    or a write file is assumed to be |.tex| unless otherwise specified; it is |transcript_extension|
    on the transcript file that records each run of \TEX; it is |.tfm| on the font metric files that
    describe characters in the fonts \TEX\ uses; it is |.dvi| on the output files that specify
    typesetting information; and it is |format_extension| on the format files written by \INITEX\
    to initialize \TEX. The file area can be arbitrary on input files, but files are usually output
    to the user's current area.

    Simple uses of \TEX\ refer only to file names that have no explicit extension or area. For
    example, a person usually says |\input paper| or |\font \tenrm = helvetica| instead of |\input
    {paper.new}| or |\font \tenrm = {test}|. Simple file names are best, because they make the \TEX\
    source files portable; whenever a file name consists entirely of letters and digits, it should be
    treated in the same way by all implementations of \TEX. However, users need the ability to refer
    to other files in their environment, especially when responding to error messages concerning
    unopenable files; therefore we want to let them use the syntax that appears in their favorite
    operating system.

    The following procedures don't allow spaces to be part of file names; but some users seem to like
    names that are spaced-out. System-dependent changes to allow such things should probably be made
    with reluctance, and only when an entire file name that includes spaces is \quote {quoted} somehow.

    Here are the global values that file names will be scanned into.

    \starttyping
    strnumber cur_name;
    strnumber cur_area;
    strnumber cur_ext;
    \stoptyping

    The file names we shall deal with have the following structure: If the name contains |/| or |:|
    (for Amiga only), the file area consists of all characters up to and including the final such
    character; otherwise the file area is null. If the remaining file name contains |.|, the file
    extension consists of all such characters from the last |.| to the end, otherwise the file
    extension is null.

    We can scan such file names easily by using two global variables that keep track of the
    occurrences of area and extension delimiters:

    Input files that can't be found in the user's area may appear in a standard system area called
    |TEX_area|. Font metric files whose areas are not given explicitly are assumed to appear in a
    standard system area called |TEX_font_area|. These system area names will, of course, vary from
    place to place.

    This whole model has been adapted a little but we do keep the |area|, |name|, |ext| distinction
    for now although we don't use the string pool.

*/

static char *tex_aux_pack_file_name(char *s, int l, const char *name, const char *ext)
{
    const char *fn = (char *) s;
    if ((! fn) || (l <= 0)) {
        fn = name;
    }
    if (! fn) {
        return NULL;
    } else if (! ext) {
        return lmt_memory_strdup(fn);
    } else {
        int e = -1;
        for (int i = 0; i < l; i++) {
            if (IS_DIR_SEP(fn[i])) {
                e = -1;
            } else if (fn[i] == '.') {
                e = i;
            }
        }
        if (e >= 0) {
            return lmt_memory_strdup(fn);
        } else {
            char *f = lmt_memory_malloc(strlen(fn) + strlen(ext) + 1);
            if (f) {
                sprintf(f, "%s%s", fn, ext);
            }
            return f;
        }
    }
}

/*tex

    Here is a routine that manufactures the output file names, assuming that |job_name <> 0|. It
    ignores and changes the current settings of |cur_area| and |cur_ext|; |s = transcript_extension|,
    |".dvi"|, or |format_extension|

    The packer does split the basename every time but isn't called that often so we can use it in
    the checker too.

*/

static char *tex_aux_pack_job_name(const char *e, int keeppath, int keepsuffix)
{
    char *n = lmt_fileio_state.job_name;
    int ln = (n) ? (int) strlen(n) : 0;
    if (! ln) {
        tex_fatal_error("bad jobname");
        return NULL;
    } else {
        int le = (e) ? (int) strlen(e) : 0;
        int f = -1; /* first */
        int l = -1; /* last */
        char *fn = NULL;
        int k = 0;
        for (int i = 0; i < ln; i++) {
            if (IS_DIR_SEP(n[i])) {
                f = i;
                l = -1;
            } else if (n[i] == '.') {
                l = i;
            }
        }
        if (keeppath) {
            f = 0;
        } else if (f < 0) {
            f = 0;
        } else {
            f += 1;
        }
        if (keepsuffix || l < 0) {
            l = ln;
        }
        fn = (char*) lmt_memory_malloc((l - f) + le + 2); /* a bit too much */
        if (fn) {
            for (int i = f; i < l; i++) {
                fn[k++] = n[i];
            }
            for (int i = 0; i < le; i++) {
                fn[k++] = e[i];
            }
            fn[k] = 0;
        }
        return fn;
    }
}

/*tex

    The following comment is obsolete but we keep it as reference because it tells some history.

    \startquotation
    Because the format is zipped we read and write dump files through zlib. Earlier versions recast
    |*f| from |FILE *| to |gzFile|, but there is no guarantee that these have the same size, so a
    static variable is needed.

    We no longer do byte-swapping so formats are generated for the system and not shared. It
    actually slowed down loading of the format on the majority of used platforms (intel).

    A \CONTEXT\ format is uncompressed some 16 MB but that used to be over 30MB due to more
    (preallocated) memory usage. A compressed format is 11 MB so the saving is not that much. If
    we were in lua I'd load the whole file in one go and use a fast decompression after which we
    could access the bytes in memory. But it's not worth the trouble.

    Tests has shown that a level 3 compression is the most optimal tradeoff between file size and
    load time.

    So, in principle we can undefine |FMT_COMPRESSION| below and experiment a bit with it. With
    SSD's it makes no dent, but on a network it still might.

    Per end May 2019 the |FMT_COMPRESSION| branch is gone so that we can simplify the opener and
    closer.
    \stopquotation

*/

void tex_check_fmt_name(void)
{
    if (lmt_engine_state.dump_name) {
        char *tmp = lmt_fileio_state.job_name;
        lmt_fileio_state.job_name = lmt_engine_state.dump_name;
        lmt_fileio_state.fmt_name = tex_aux_pack_job_name(format_extension, 1, 0);
        lmt_fileio_state.job_name = tmp;
    } else if (lmt_main_state.run_state != initializing_state) {
        /*tex For |dump_name| to be NULL is a bug. */
        tex_emergency_message("startup error", "no format file given, quitting");
        tex_emergency_exit();
    }
}

void tex_check_job_name(char * fn)
{
    if (! lmt_fileio_state.job_name) {
        if (lmt_engine_state.startup_jobname) {
            lmt_fileio_state.job_name = lmt_engine_state.startup_jobname; /* not freed here */
            lmt_fileio_state.job_name = tex_aux_pack_job_name(NULL, 0, 0);
        } else if (fn) {
            lmt_fileio_state.job_name = fn;
            lmt_fileio_state.job_name = tex_aux_pack_job_name(NULL, 0, 0); /* not freed here */
        } else {
            tex_emergency_message("startup warning", "using fallback jobname 'texput', continuing");
            lmt_fileio_state.job_name = lmt_memory_strdup("texput");
        }
    }
    if (! lmt_fileio_state.log_name) {
        lmt_fileio_state.log_name = tex_aux_pack_job_name(transcript_extension, 0, 1);
    }
    if (! lmt_fileio_state.fmt_name) {
        lmt_fileio_state.fmt_name = tex_aux_pack_job_name(format_extension, 0, 1);
    }
}

/*tex

    A messier routine is also needed, since format file names must be scanned before \TEX's
    string mechanism has been initialized. We shall use the global variable |TEX_format_default|
    to supply the text for default system areas and extensions related to format files.

    Under \UNIX\ we don't give the area part, instead depending on the path searching that will
    happen during file opening. Also, the length will be set in the main program.

    \starttyping
    char *TEX_format_default;
    \stoptyping

    This part of the program becomes active when a \quote {virgin} \TEX\ is trying to get going,
    just after the preliminary initialization, or when the user is substituting another format file
    by typing |&| after the initial |**| prompt. The buffer contains the first line of input in
    |buffer[loc .. (last - 1)]|, where |loc < last| and |buffer[loc] <> " "|.

*/

dumpstream tex_open_fmt_file(int writemode)
{
    dumpstream f = NULL;
    if (! lmt_fileio_state.fmt_name) {
        /* this can't happen */
        tex_emergency_message("startup error", "no format output file '%s' given, quitting", emergency_fmt_name);
        tex_emergency_exit();
    } else if (writemode) {
        f = aux_utf8_fopen(lmt_fileio_state.fmt_name, FOPEN_WBIN_MODE);
        if (! f) {
            tex_emergency_message("startup error", "invalid format output file '%s' given, quitting", lmt_fileio_state.fmt_name);
            tex_emergency_exit();
        }
    } else {
        int callbackid = lmt_callback_defined(find_format_file_callback);
        if (callbackid > 0) {
            char *fnam = NULL;
            int test = lmt_run_callback(lmt_lua_state.lua_instance, callbackid, "S->R", lmt_fileio_state.fmt_name, &fnam);
            if (test && fnam && strlen(fnam) > 0) {
                lmt_memory_free(lmt_fileio_state.fmt_name);
                lmt_fileio_state.fmt_name = fnam;
            } else {
                lmt_memory_free(fnam);
            }
            f = aux_utf8_fopen(lmt_fileio_state.fmt_name, FOPEN_RBIN_MODE);
            if (! f) {
                tex_emergency_message("startup error", "invalid format input file '%s' given, quitting", emergency_fmt_name);
                tex_emergency_exit();
            }
        } else {
            /*tex For the moment we make this mandate! */
            tex_emergency_message("startup error", "missing find_format_file callback");
            tex_emergency_exit();
        }
    }
    return f;
}

void tex_close_fmt_file(dumpstream f)
{
    if (f) {
        fclose(f);
    }
}

/*tex

    The variable |name_in_progress| is used to prevent recursive use of |scan_file_name|, since the
    |begin_name| and other procedures communicate via global variables. Recursion would arise only
    by devious tricks like |\input \input f|; such attempts at sabotage must be thwarted.
    Furthermore, |name_in_progress| prevents |\input| from being initiated when a font size
    specification is being scanned.

    Another variable, |job_name|, contains the file name that was first |\input| by the user. This
    name is extended by |transcript_extension| and |.dvi| and |format_extension| in the names of
    \TEX's output files. The fact if the transcript file been opened is registered in
    |log_opened_global|.

    Initially |job_name = 0|; it becomes nonzero as soon as the true name is known. We have
    |job_name = 0| if and only if the |log| file has not been opened, except of course for a short
    time just after |job_name| has become nonzero.

    The full name of the log file is stored in |log_name|. The |open_log_file| routine is used to
    open the transcript file and to help it catch up to what has previously been printed on the
    terminal.

*/

void tex_open_log_file(void)
{
    if (! lmt_fileio_state.log_opened) {
        int callback_id = lmt_callback_defined(find_log_file_callback);
        if (callback_id > 0) {
            char *filename = NULL;
            int okay = 0;
            tex_check_job_name(NULL);
            okay = lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "S->R", lmt_fileio_state.log_name, &filename);
            if (okay && filename && (strlen(filename) > 0)) {
                lmt_memory_free(lmt_fileio_state.log_name);
                lmt_fileio_state.log_name = filename;
            } else {
                lmt_memory_free(filename);
            }
        } else {
            /*tex For the moment we make this mandate! */
            tex_emergency_message("startup error", "missing find_log_file callback");
            tex_emergency_exit();
        }
        if (tex_aux_open_outfile(&lmt_print_state.logfile, lmt_fileio_state.log_name, FOPEN_W_MODE)) {
            /*tex The previous |selector| setting is saved:*/
            int saved_selector = lmt_print_state.selector;
            lmt_print_state.selector = logfile_selector_code;
            lmt_fileio_state.log_opened = 1;
            /*tex Again we resolve a callback id: */
            callback_id = lmt_callback_defined(start_run_callback);
            /*tex There is no need to free |fn|! */
            if (callback_id == 0) {
                tex_print_banner();
                /*tex Print the banner line, including current date and time. */
                tex_print_log_banner();
                /*tex Make sure bottom level is in memory. */
                lmt_input_state.input_stack[lmt_input_state.input_stack_data.ptr] = lmt_input_state.cur_input;
                /*tex We don't have a first line so that code is gone. */
                tex_print_ln();
            } else if (callback_id > 0) {
                lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "->");
            } else {
                tex_print_banner();
            }
            /*tex should be done always */
            if (lmt_print_state.loggable_info) {
                fprintf(lmt_print_state.logfile, "%s\n", lmt_print_state.loggable_info);
                lmt_memory_free(lmt_print_state.loggable_info);
                lmt_print_state.loggable_info = NULL;
            }
            switch (saved_selector) {
                case no_print_selector_code : lmt_print_state.selector = logfile_selector_code; break;
                case terminal_selector_code : lmt_print_state.selector = terminal_and_logfile_selector_code; break;
                default                     : lmt_print_state.selector = saved_selector; break;
            }
        } else {
            tex_emergency_message("startup error", "log file '%s' cannot be opened, quitting", emergency_log_name);
            tex_emergency_exit();
        }
    }
}

void tex_close_log_file(void)
{
    fclose(lmt_print_state.logfile);
    lmt_fileio_state.log_opened = 0;
}

/*tex

    Let's turn now to the procedure that is used to initiate file reading when an |\input| command
    is being processed. This function is used with |\\input| as well as in the start up.

*/

void tex_start_input(char *fn)
{
    /*tex Set up |cur_file| and new level of input. */
    tex_begin_file_reading();
    if (! tex_lua_a_open_in(fn)) {
        /*tex
            Normally this is catched earler, as we have lookup callbacks but the first file, the
            one passed on the command line can fall though this checking.
        */
        tex_end_file_reading();
        tex_emergency_message("runtime error", "input file '%s' is not found, quitting", fn);
        tex_emergency_exit();
    }
    lmt_input_state.in_stack[lmt_input_state.in_stack_data.ptr].full_source_filename = fn;
    lmt_input_state.cur_input.name = io_file_input_code;
    /*tex
        |open_log_file| doesn't |show_context|, so |limit| and |loc| needn't be set to meaningful
        values yet.
    */
    tex_report_start_file((unsigned char *) fn);
    ++lmt_input_state.open_files;
    tex_terminal_update();
    lmt_input_state.cur_input.state = new_line_state;
    /*tex

        Read the first line of the new file. Here we have to remember to tell the |lua_input_ln|
        routine not to start with a |get|. If the file is empty, it is considered to contain a
        single blank line.

    */
    lmt_input_state.input_line = 1;
    tex_lua_input_ln();
    lmt_input_state.cur_input.limit = lmt_fileio_state.io_last; /*tex Was |firm_up_the_line();|. */
    if (end_line_char_inactive) {
        --lmt_input_state.cur_input.limit;
    } else {
        lmt_fileio_state.io_buffer[lmt_input_state.cur_input.limit] = (unsigned char) end_line_char_par;
    }
    lmt_fileio_state.io_first = lmt_input_state.cur_input.limit + 1;
    lmt_input_state.cur_input.loc = lmt_input_state.cur_input.start;
}

/*tex

    In order to isolate the system-dependent aspects of file names, the system-independent parts of
    \TEX\ are expressed in terms of three system-dependent procedures called |begin_name|,
    |more_name|, and |end_name|. In essence, if the user-specified characters of the file name are
    |c_1|\unknown|c_n|, the system-independent driver program does the operations

    \starttyping
    |begin_name|;
    |more_name|(c_1);
    .....
    |more_name|(c_n);
    |end_name|
    \stoptyping

    These three procedures communicate with each other via global variables. Afterwards the file
    name will appear in the string pool as three strings called |cur_name|, |cur_area|, and
    |cur_ext|; the latter two are null (i.e., |""|), unless they were explicitly specified by the
    user.

    Actually the situation is slightly more complicated, because \TEX\ needs to know when the file
    name ends. The |more_name| routine is a function (with side effects) that returns |true| on the
    calls |more_name (c_1)|, \dots, |more_name (c_{n - 1})|. The final call |more_name(c_n)| returns
    |false|; or, it returns |true| and the token following |c_n| is something like |\hbox| (i.e.,
    not a character). In other words, |more_name| is supposed to return |true| unless it is sure that
    the file name has been completely scanned; and |end_name| is supposed to be able to finish the
    assembly of |cur_name|, |cur_area|, and |cur_ext| regardless of whether |more_name (c_n)|
    returned |true| or |false|.

    This code has been adapted and the string pool is no longer used. We also don't ask for another
    name on the console.

*/

/*tex

    And here's the second. The string pool might change as the file name is being scanned, since a
    new |\csname| might be entered; therefore we keep |area_delimiter| and |ext_delimiter| relative
    to the beginning of the current string, instead of assigning an absolute address like |pool_ptr|
    to them.

    Now let's consider the \quote {driver} routines by which \TEX\ deals with file names in a
    system-independent manner. First comes a procedure that looks for a file name in the input by
    calling |get_x_token| for the information.

*/

char *tex_read_file_name(int optionalequal, const char * name, const char* ext)
{
    char *fn = NULL;
    int l = 0;
    char *s = NULL;
    halfword result;
    if (optionalequal) {
        tex_scan_optional_equals();
    }
    do {
        tex_get_x_token();
    } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd);
    if (cur_cmd == left_brace_cmd) {
        result = tex_scan_toks_expand(1, NULL, 0);
    } else {
        int quote = 0;
        halfword p = get_reference_token();
        result = p;
        while (1) {
            switch (cur_cmd) {
                case escape_cmd:
                case left_brace_cmd:
                case right_brace_cmd:
                case math_shift_cmd:
                case alignment_tab_cmd:
                case parameter_cmd:
                case superscript_cmd:
                case subscript_cmd:
                case letter_cmd:
                case other_char_cmd:
                    if (cur_chr == '"') {
                        if (quote) {
                            goto DONE;
                        } else {
                            quote = 1;
                        }
                    } else {
                         p = tex_store_new_token(p, cur_tok);
                    }
                    break;
                case spacer_cmd:
                case end_line_cmd:
                    if (quote) {
                        p = tex_store_new_token(p, token_val(spacer_cmd, ' '));
                    } else {
                        goto DONE;
                    }
                case ignore_cmd:
                    break;
                default:
                    tex_back_input(cur_tok);
                    goto DONE;
            }
            tex_get_x_token();
        }
    }
  DONE:
    s = tex_tokenlist_to_tstring(result, 1, &l, 0, 0, 0);
    fn = s ? tex_aux_pack_file_name(s, l, name, ext) : NULL;
    /*tex Shouldn't we also free |result| ? */
    tex_flush_token_list(result);
    return fn;
}

void tex_print_file_name(unsigned char *name)
{
    int must_quote = 0;
    if (name) {
        unsigned char *j = name;
        while (*j) {
            if (*j == ' ') {
                must_quote = 1;
                break;
            } else {
                j++;
            }
        }
    }
    if (must_quote) {
        /* initial quote */
        tex_print_char('"');
    }
    if (name) {
        unsigned char *j = name;
        while (*j) {
            if (*j == '"') {
                /* skip embedded quote, maybe escape */
            } else {
                tex_print_char(*j);
            }
            j++;
        }
    }
    if (must_quote) {
        /* final quote */
        tex_print_char('"');
    }
}

void tex_report_start_file(unsigned char *name)
{
    int callback_id = lmt_callback_defined(start_file_callback);
    if (callback_id) {
        lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "S->", name);
    } else {
        tex_print_char('(');
        tex_print_file_name((unsigned char *) name);
    }
}

void tex_report_stop_file(void)
{
    int callback_id = lmt_callback_defined(stop_file_callback);
    if (callback_id) {
        lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "->");
    } else {
        tex_print_char(')');
    }
}