💾 Archived View for runjimmyrunrunyoufuckerrun.com › src › foreign › pmw › src › read1.c captured on 2021-12-17 at 13:26:06.
View Raw
More Information
-=-=-=-=-=-=-
#/*************************************************
- The PMW Music Typesetter - 2nd incarnation *
- ************************************************/
/* Copyright (c) Philip Hazel, 1991 - 2021 */
/* Written by Philip Hazel, starting November 1991 */
/* This file last modified: January 2021 */
/* This file contains part I of the code for reading in a PMW score file. */
#include "pmwhdr.h"
#include "readhdr.h"
#include "pagehdr.h"
#include "outhdr.h"
/*************************************************
- Local static variables *
- ************************************************/
static usint read_staves[STAVE_BITVEC_SIZE]; /* Map of staves actually read */
static int read_prevstave; /* Previous stave number */
static int read_count;
static BOOL read_in_quotes;
static BOOL read_stavestart; /* True if expecting staff start */
/* A dummy page structure specially for the string escape mechanism which is
called to check strings and may need to handle a page number. */
static pagestr dummy_page = { NULL, NULL, NULL, 1, 0, 0, 0 };
/* Default "macro" for the &* replication feature */
static macrostr replicate_macro = { 1, US"&1", { US"" } };
/*************************************************
- Expand string with macros substitutions *
- ************************************************/
/* This is called for input lines, and also for the arguments of nested macro
calls. It copies the input string, expanding any macros encountered. If a
comment character is encountered, the rest of the line is copied, for error
messages, but the returned end value does not include the comment.
Arguments:
inptr point to the start of the string
inend point one past the end of the string
outbuffer where to put the expanded line
outlen length of the output buffer
addNL if TRUE, add a newline character on the end
Returns: pointer one past the end of the active part of the
expanded string in outbuffer
static uschar *
expand_string(uschar *inptr, uschar *inend, uschar *outbuffer, int outlen,
BOOL addNL)
{
uschar *temp = outbuffer;
uschar *comment = NULL;
while (inptr < inend)
{
int ch = *inptr++;
/* Keep track of quotes; can't have comments in quotes */
if (ch == '\"')
{
read_in_quotes = !read_in_quotes;
*temp++ = ch;
}
/* After a comment character, just copy over the rest of the input (for error
messages) noting where the comment started. */
else if (!read_in_quotes && ch == '@')
{
comment = temp;
*temp++ = ch;
while (inptr < inend) *temp++ = *inptr++;
break;
}
/* Deal with defined insertions, possibly with arguments. Also deal with &*()
repetitions, which uses very similar code. */
else if (ch == '&')
{
if (*inptr == '&') *temp++ = *inptr++; else
{
macrostr *mm = NULL;
BOOL had_semicolon = FALSE;
usint count = 1;
/* Set up for a macro call. Note that macro names may start with a digit
and are case-sensitive, so we can't use next_word(). */
if (isalnum(*inptr))
{
int i = 0;
tree_node *s;
uschar name[WORD_BUFFERSIZE];
name[i++] = *inptr++;
while (isalnum(*inptr))
{
if (i >= WORD_BUFFERSIZE - 1)
error_moan(ERR136, "Macro name", WORD_BUFFERSIZE - 1); /* Hard */
name[i++] = *inptr++;
}
name[i] = 0;
if (*inptr == ';')
{
inptr++; /* Optional semicolon after name is */
had_semicolon = TRUE; /* skipped, and no args allowed */
}
if ((s = Tree_Search(define_tree, name)) != NULL)
{
mm = (macrostr *)s->data; /* Will be NULL for no replacement */
if (mm == NULL) goto END_MACRO;
}
else
{
error_moan(ERR9, name); /* Couldn't find name */
goto END_MACRO;
}
}
/* Set up for a replication call */
else if (*inptr == '*')
{
int len;
if (sscanf((char *)(++inptr), "%u(%n", &count, &len) <= 0)
{
error_moan(ERR10, "Unsigned number followed by \"(\"");
goto END_MACRO;
}
inptr += len - 1;
mm = &replicate_macro;
}
else
{
error_moan(ERR8); /* Bad uschar after '&' */
goto END_MACRO;
}
/* Found a macro or &*<digits>. For a macro, mm points to its data, and
count is 1. For a replication, mm points to a dummy with 1 empty default
argument, count contains the replication count, and we know there is an
argument. */
/* Optimize the case when macro is defined with no arguments */
if (mm->argcount == 0)
{
int k = Ustrlen(mm->text);
if (outlen - 2 - (temp - outbuffer) < k + (inend - inptr))
error_moan(ERR88); /* hard error - buffer overflow */
Ustrcpy(temp, mm->text);
temp += k;
}
/* Otherwise we have to process char by char, and read arguments, if
any. There need not be; they can all be defaulted. Arguments are read,
serially, into argbuff, and then expanded for nested macros into what
remains of argbuff. */
else
{
int i;
int argcount = mm->argcount;
uschar *args[MAX_MACROARGS];
uschar argbuff[4*READ_BUFFERSIZE];
uschar *ap = argbuff;
/* Set up the default arguments */
for (i = 0; i < argcount; i++) args[i] = mm->args[i];
/* Read given arguments, if any, increasing the count if more
than the default number. */
if (!had_semicolon && *inptr == '(')
{
for (i = 0;; i++)
{
int bracount = 0;
BOOL inquotes = FALSE;
uschar *ss = ap;
if (argcount >= MAX_MACROARGS)
error_moan(ERR137, MAX_MACROARGS); /* Hard */
while (++inptr < inend &&
((*inptr != ',' && *inptr != ')') ||
bracount > 0 || inquotes))
{
int cch = *inptr;
if (cch == '&' && !isalnum(inptr[1]) && inptr[1] != '*')
*ap++ = *(++inptr);
else
{
if (cch == '\"') inquotes = !inquotes;
if (!inquotes)
{
if (cch == '(') bracount++;
else if (cch == ')') bracount--;
}
*ap++ = cch;
}
}
if (inptr >= inend) error_moan(ERR99);
if (i >= argcount)
{
args[i] = NULL;
argcount++;
}
if (ap - ss > 0)
{
*ap++ = 0;
args[i] = ss;
}
if (inptr >= inend || *inptr == ')')
{
inptr++;
break;
}
}
/* Only one argument is currently allowed for a replication. Any
others are ignored. */
if (mm == &replicate_macro && argcount > 1) error_moan(ERR134);
}
/* Process the arguments for nested macro calls */
for (i = 0; i < argcount; i++)
{
uschar *new_ap;
if (args[i] == NULL || Ustrchr(args[i], '&') == NULL) continue;
new_ap = expand_string(args[i], args[i] + Ustrlen(args[i]),
ap, argbuff + sizeof(argbuff) - ap, FALSE);
args[i] = ap;
ap = new_ap + 1; /* final zero must remain */
}
/* Now copy the replacement, inserting the args. For a replication we
repeat many times. For a macro, count is always 1. */
while (count-- > 0)
{
uschar *pp = mm->text;
while (*pp != 0)
{
if (*pp == '&' && isdigit(pp[1]))
{
int arg = 0;
while (isdigit(*(++pp))) arg = arg*10 + *pp - '0';
if (*pp == ';') pp++;
if (--arg < argcount)
{
uschar *ss = args[arg];
if (ss != NULL)
{
if (READ_BUFFERSIZE - 2 - (temp - outbuffer) <
Ustrlen(ss)) error_moan(ERR88); /* hard */
Ustrcpy(temp, ss);
temp += Ustrlen(ss);
}
}
}
else
{
if (temp - outbuffer + 2 >= outlen)
error_moan(ERR88); /* hard error - buffer overflow */
*temp++ = *pp++;
}
}
}
}
/* Macro/replication processing is done */
END_MACRO: continue;
}
}
/* Otherwise it is a normal character */
else *temp++ = ch;
}
/* For whole lines, keep buffer as a NL-terminated string for debugging */
if (addNL)
{
temp[0] = '\n';
temp[1] = 0;
}
else *temp = 0;
/* Return the end of the active data */
return comment? comment : temp;
}
/*************************************************
- Read next character *
- ************************************************/
/* This function updates the global variable read_ch with the next character,
including a newline at the end of each line. It deals with macro expansions and
preprocessing directives, and it skips comments.
Arguments: none
Returns: nothing
void
next_ch(void)
{
for (;;) /* Loop until a character is obtained or very end is reached */
{
int len;
/* Test for more chars in the current line. If not, return '\n' at end of
line (it's not in the data because that may actually end with an '@' for a
comment). */
if (read_chptr < read_endptr) { read_ch = *read_chptr++; return; }
if (read_chptr++ == read_endptr) { read_ch = '\n'; return; }
/* Copy the line just finished into the previous buffer, for use by the error
printing routine, unless this line was empty. */
if (this_buffer[0] != 0 && this_buffer[0] != '\n')
{
uschar *temp = prev_buffer;
prev_buffer = this_buffer;
this_buffer = temp;
}
/* Get next logical line, joining together physical lines that end with &&&.
At end of file, check for missing "fi"s and deal with included files. */
for (;;) /* Loop for included files */
{
if (input_file != NULL)
{
BOOL line_read = FALSE;
uschar *tbuffer = this_buffer;
int size = READ_BUFFERSIZE - 4;
len = 0;
for (;;) /* Loop for concatenated lines */
{
if (Ufgets(tbuffer, size, input_file) != NULL)
{
int tlen = Ustrlen(tbuffer);
line_read = TRUE;
read_linenumber++;
len += tlen;
if (tlen < 4 || Ustrcmp(tbuffer + tlen - 4, "&&&\n") != 0)
goto PROCESS_LINE;
len -= 4;
tlen -= 4;
tbuffer += tlen;
*tbuffer = 0;
size -= tlen;
}
else /* Reached EOF */
{
if (line_read) goto PROCESS_LINE; else break;
}
}
/* No lines read */
fclose(input_file);
input_file = NULL;
}
/* Handle reaching the end of an input file */
Ustrcpy(this_buffer, "--- End of file ---"); /* for reflection */
read_chptr = this_buffer + 19; /* just in case */
read_endptr = read_chptr + 1; /* nothing left */
if (read_skipdepth > 0 || read_okdepth > 0) error_moan(ERR18);
/* Real end */
if (read_filestackptr <= 0)
{
read_EOF = TRUE;
read_ch = EOF;
return;
}
/* Pop stack at end of included file */
DEBUG(("end of %s: popping include stack\n", main_filename));
store_free(main_filename);
main_filename = read_filestack[--read_filestackptr].filename;
input_file = read_filestack[read_filestackptr].file;
read_linenumber = read_filestack[read_filestackptr].linenumber;
read_okdepth = read_filestack[read_filestackptr].okdepth;
read_skipdepth = 0;
}
/* Another line has been read; take care with the final one which may not
have a newline on the end. Nor will a final line that is just "&&&\n". */
PROCESS_LINE:
read_count += len;
read_chptr = this_buffer;
read_endptr = this_buffer + len;
if (len >= READ_BUFFERSIZE - 5) error_moan(ERR81); /* give-up error */
if (len > 0 && read_endptr[-1] == '\n') read_endptr--;
/* Scan the line for comment and defined names, copying into the next buffer.
The working buffer is always this_buffer, so that error messages can reflect
it. However, if skipping lines, skip this processing too. */
if (read_skipdepth <= 0)
{
uschar *temp;
read_endptr = expand_string(read_chptr, read_endptr, next_buffer,
READ_BUFFERSIZE, TRUE);
/* Swap this buffer and next buffer, initialize pointer. */
temp = this_buffer;
this_buffer = next_buffer;
next_buffer = temp;
read_chptr = this_buffer;
}
/* If this buffer begins with '*', it is a pre-processing directive. We
process it, and treat as a null line. Set up read_ch before preprocessing,
so that code can call normal item reading routines. Clear the in-quotes flag,
because a *define can legitimately have unmatched quotes, and no preprocessor
directive can in any case have a quoted string that runs over onto the next
line. */
while (*read_chptr == ' ' || *read_chptr == '\t') read_chptr++;
if (*read_chptr++ == '*')
{
if (isalpha(read_ch = *read_chptr++)) pre_process(); else error_moan(ERR12);
read_chptr = read_endptr;
read_in_quotes = FALSE;
}
else read_chptr = (read_skipdepth > 0)? read_endptr : this_buffer;
}
}
/*************************************************
- Read and lowercase next word *
- ************************************************/
/*
Returns: nothing
void
next_word(void)
{
int i = 0;
sigch();
if (isalpha(read_ch))
{
do
{
if (i >= WORD_BUFFERSIZE - 1)
error_moan(ERR136, "Word", WORD_BUFFERSIZE - 1); /* Hard */
read_word[i++] = tolower(read_ch);
next_ch();
}
while (isalnum(read_ch) || read_ch == '_');
}
read_word[i] = 0;
}
/*************************************************
- Read plain string *
- ************************************************/
/* This procedure is used for reading file names and the like
in heading directives. These are in quotes, but are not to be
interpreted as PMW strings. They are stored in read_word.
Returns: TRUE if OK, FALSE if starting quote missing
BOOL
read_plainstring(void)
{
int i = 0;
sigch();
if (read_ch != '\"') return FALSE;
next_ch();
while (read_ch != '\"' && read_ch != '\n')
{
if (i >= WORD_BUFFERSIZE - 1)
error_moan(ERR136, "String", WORD_BUFFERSIZE - 1); /* Hard */
read_word[i++] = read_ch;
next_ch();
}
read_word[i] = 0;
if (read_ch == '\"') next_ch();
else error_moan(ERR16, "Terminating quote missing");
return TRUE;
}
/*************************************************
- Read integer or fixed point number *
- ************************************************/
/* Called when the first character is known to be a digit or a dot.
Argument: TRUE to read a fixed point number; FALSE for an integer
Returns: the value
int
read_integer(BOOL fixed)
{
int yield = 0;
while (isdigit(read_ch))
{
yield = yield*10 + read_ch - '0';
next_ch();
}
if (fixed)
{
yield *= 1000;
if (read_ch == '.')
{
int d = 100;
while (next_ch(), isdigit(read_ch))
{
yield += (read_ch - '0')*d;
d /= 10;
}
}
}
return yield;
}
/*************************************************
- Read an expected int or fixed *
- ************************************************/
/* This is called when the first character hasn't been
checked, and an error indication is required if the value is missing.
Arguments:
yield where to put the value
fixed TRUE for a fixed point value, FALSE for an integer
allowsign TRUE if a sign is permitted
Returns: TRUE if OK, FALSE on error
BOOL
read_expect_integer(int *yield, int fixed, int allowsign)
{
int sign = 1;
sigch();
if (allowsign)
{
if (read_ch == '+') next_ch();
else if (read_ch == '-') { sign = -1; next_ch(); }
}
if (!isdigit(read_ch))
{
error_moan(ERR10, allowsign? "Number" : "Unsigned number");
return FALSE;
}
- yield = sign * read_integer(fixed);
return TRUE;
}
/*************************************************
- Read an expected movement dimension *
- ************************************************/
/* This function is called after /u, /d, /l, or /r has been read.
Arguments: none
Returns: the value, or zero after an error
int
read_movevalue(void)
{
int x;
next_ch();
return (read_expect_integer(&x, TRUE, FALSE))? x : 0;
}
/*************************************************
- Read key signature *
- ************************************************/
/*
Arguments: none
Returns: the key signature (C major after an error)
int
read_key(void)
{
int key = C_key;
sigch();
read_ch = tolower(read_ch);
if ('a' <= read_ch && read_ch <= 'g')
{
key = read_ch - 'a';
next_ch();
if (read_ch == '#') { key += 7; next_ch(); }
else if (read_ch == '