[metapost] Still experimenting with MPlib

luigi scarso luigi.scarso at gmail.com
Wed Dec 14 18:12:48 CET 2016


On Tue, Dec 13, 2016 at 10:29 AM, Nicola <nvitacolonna at gmail.com> wrote:
> Hi,
> I'm still experimenting with MPlib, and there is something that I do not
> understand. I hope you can help me. I have tried the following program:
>
> void imp_init_mp_instance()
> {
>   MP_options *opt = mp_options();
>   opt->ini_version = 0;
>   opt->mem_name = "mpost";
>   opt->command_line = NULL;
>   opt->noninteractive = 1;
>   imp = mp_initialize(opt);
>   if (!imp) { exit(EXIT_FAILURE); }
>   mp_execute(imp, "\\", 1); // Flush MetaPost introductory text
> }
>
>
> int main(void)
> {
>   imp_init_mp_instance();
>
>   char *line[] = {
>     "if 1=0:",
>     "message \"tsk\";",
>     "fi;"
>   };
>
>   for (int i = 0; i < 3; i++) {
>     mp_execute(imp, line[i], strlen(line[i]));
>     mp_run_data *cmd_result = mp_rundata(imp);
>     printf("%s", cmd_result->term_out.data);
>     printf("Status: %d\n", mp_status(imp));
>   }
> }
>
> This results in the MP instance being in an error state, and the error
> message is the following:
>
> ! Missing `:' has been inserted.
> <inserted text>
>                 :
> <to be read again>
>                    fi
> <*> fi
>       ;
> ! A statement can't begin with `:'.
> <to be read again>
>                    :
> <to be read again>
>                    fi
> <*> fi
>       ;
> ! Extra tokens will be flushed.
> <to be read again>
>                    :
> <to be read again>
>                    fi
> <*> fi
>       ;
>
> Of course, this doesn't work as in mpost's prompt, as I had naïvely
> expected. So, should mp_execute() be fed with complete statements only?
>
> It is not clear to me what happens internally when mp_execute() is passed an
> incomplete statement. For example, if I call:
>
> mp_execute(imp, "x=1", 3); // Note the missing semi-colon
> mp_execute(imp, "show x;", 7);
>
> this acts as if x=1 was ignored (`show x` prints `>> x`). But the example
> above seems to indicate that this is not the case (if incomplete statements
> were ignored, I'd get an `! Extra fi.` error after the third invocation).
>
>
>
> Nicola
>
> --
> http://tug.org/metapost/

Basically, when mpost needs something, it reads the rest of the file
or asks the user to put more material.
In your program there is no file (only strings) and no user, and in
this case the "if" needs to read tokens until
the "else", "elseif" or "fi" , but it cannot do it because there is
still nothing to read .

In detail (see mp.w, rev. 2106 ):

#0  mp_pass_text (mp=0x96f280) at ../../../source/texk/web2c/mplibdir/mp.w:20784
#1  0x0000000000458640 in mp_conditional (mp=0x96f280) at
../../../source/texk/web2c/mplibdir/mp.w:20931
#2  0x000000000045557f in mp_expand (mp=0x96f280) at
../../../source/texk/web2c/mplibdir/mp.w:19818
#3  0x00000000004568e1 in mp_get_x_next (mp=0x96f280) at
../../../source/texk/web2c/mplibdir/mp.w:20199
#4  0x0000000000477e22 in mp_do_statement (mp=0x96f280) at
../../../source/texk/web2c/mplibdir/mp.w:29301
#5  0x000000000047c78a in mp_execute (mp=0x96f280, s=0x70f713 "if
1=0:", l=7) at ../../../source/texk/web2c/mplibdir/mp.w:30695
#6  0x0000000000403792 in main () at main.c:49


@ Here is a procedure that ignores text until coming to an \&{elseif},
\&{else}, or \&{fi} at level zero of $\&{if}\ldots\&{fi}$
nesting. After it has acted, |cur_mod| will indicate the token that
was found.

\MP's smallest two command codes are |if_test| and |fi_or_else|; this
makes the skipping process a bit simpler.

@c
void mp_pass_text (MP mp) {
  integer l = 0;
  mp->scanner_status = skipping;
  mp->warning_line = mp_true_line (mp);
  while (1) {
    get_t_next (mp);
    if (cur_cmd() <= mp_fi_or_else) {
      if (cur_cmd() < mp_fi_or_else) {
        incr (l);
      } else {
        if (l == 0)
          break;
        if (cur_mod() == fi_code)
          decr (l);
      }
    } else {
      @<Decrease the string reference count,
       if the current token is a string@>;
    }
  }
  mp->scanner_status = normal;
}


When we call
get_t_next (mp);

the buffer is

p mp->buffer
$2 = (ASCII_code *) 0x974aa0 "if 1=0:%a..."

The '%'  is added by mpost to mark the end of the line;
mp_get_next asks a new chunk

@c
void mp_get_next (MP mp) {
  /* sets |cur_cmd|, |cur_mod|, |cur_sym| to next token */
  mp_sym cur_sym_;    /* speed up access */
RESTART:
  set_cur_sym(NULL);
  set_cur_sym_mod(0);
  if (file_state) {
    int k;        /* an index into |buffer| */
    ASCII_code c; /* the current character in the buffer */
    int cclass;    /* its class number */
    /* Input from external file; |goto restart| if no input found,
       or |return| if a non-symbolic token is found */
    /* A percent sign appears in |buffer[limit]|; this makes it unnecessary
       to have a special test for end-of-line. */
  SWITCH:
    c = mp->buffer[loc];
    incr (loc);
    cclass = mp->char_class[c];
    switch (cclass) {
    case digit_class:
      scan_numeric_token((c - '0'));
      return;
      break;
    case period_class:
      cclass = mp->char_class[mp->buffer[loc]];
      if (cclass > period_class) {
        goto SWITCH;
      } else if (cclass < period_class) {  /* |class=digit_class| */
        scan_fractional_token(0);
        return;
      }
      break;
    case space_class:
      goto SWITCH;
      break;
    case percent_class:
      if (mp->scanner_status == tex_flushing) {
        if (loc < limit)
          goto SWITCH;
      }
      /* Move to next line of file, or |goto restart| if there is no
next line */
      switch (move_to_next_line(mp)) {
      case 1:  goto RESTART;       break;
      case 2:  goto COMMON_ENDING; break;
      default: break;
      }
:


Here the program fails in move_to_next_line(mp) because
the name of the current file is NULL and we should stop at prompt_input

18955       if (mp->input_ptr > 0) {
18956         /* text was inserted during error recovery or by \&{scantokens} */
18957         mp_end_file_reading (mp);
18958         /* goto RESTART */
 18959         return 1;               /* resume previous level */
 18960       }
 18961       if (mp->job_name == NULL
 18962           && (mp->selector < log_only || mp->selector >= write_file))
 18963         mp_open_log_file (mp);
 18964       if (mp->interaction > mp_nonstop_mode) {
 18965         if (limit == start)         /* previous line was empty */
 18966           mp_print_nl (mp, "(Please type a command or say `end')");
 18967         mp_print_ln (mp);
 18968         mp->first = (size_t) start;
 18969         prompt_input ("*");

But at prompt_input we execute the longjmp because mp->noninteractive

1778    void mp_term_input (MP mp) {                               /*
gets a line from the terminal */
1779      size_t k;     /* index into |buffer| */
1780      if (mp->noninteractive) {
1781        if (!mp_input_ln (mp, mp->term_in))
1782          longjmp (*(mp->jump_buf), 1);     /* chunk finished */

and we go back to mp_execute with return mp->history = 0
(which is not ok, btw)

30651   int mp_execute (MP mp, char *s, size_t l) {
30652     mp_reset_stream (&(mp->run_data.term_out));
30653     mp_reset_stream (&(mp->run_data.log_out));
30654     mp_reset_stream (&(mp->run_data.error_out));
30655     mp_reset_stream (&(mp->run_data.ship_out));
30656     if (mp->finished) {
30657       return mp->history;
30658     } else if (!mp->noninteractive) {
30659       mp->history = mp_fatal_error_stop;
30660       return mp->history;
30661     }
30662     if (mp->history < mp_fatal_error_stop) {
30663       xfree (mp->jump_buf);
30664       mp->jump_buf = malloc (sizeof (jmp_buf));
30665       if (mp->jump_buf == NULL || setjmp (*(mp->jump_buf)) != 0) {
30666         return mp->history;
30667       }


-- 
luigi



More information about the metapost mailing list