// -*- C++ -*-
/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
Written by James Clark (jjc@jclark.com)
This file is part of groff.
groff is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or
(at your option) any later version.
groff is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#include "pic.h"
#include "ptable.h"
#include "object.h"
#include "pic.hpp"
declare_ptable(char)
implement_ptable(char)
PTABLE(char) macro_table;
// First character of the range representing $1-$.
// All of them must be invalid input characters.
#ifndef IS_EBCDIC_HOST
#define ARG1 0x80
#define MAX_ARG 32
#else
#define ARG1 0x30
#define MAX_ARG 16
#endif
class macro_input : public input {
char *s;
char *p;
public:
macro_input(const char *);
~macro_input();
int get();
int peek();
};
class argument_macro_input : public input {
char *s;
char *p;
char *ap;
int argc;
char *argv[MAX_ARG];
public:
argument_macro_input(const char *, int, char **);
~argument_macro_input();
int get();
int peek();
};
input::input() : next(0)
{
}
input::~input()
{
}
int input::get_location(const char **, int *)
{
return 0;
}
file_input::file_input(FILE *f, const char *fn)
: fp(f), filename(fn), lineno(0), ptr("")
{
}
file_input::~file_input()
{
fclose(fp);
}
int file_input::read_line()
{
for (;;) {
line.clear();
lineno++;
for (;;) {
int c = getc(fp);
if (c == '\r') {
c = getc(fp);
if (c != '\n')
lex_error("invalid input character code %1", '\r');
}
if (c == EOF)
break;
else if (is_invalid_input_char(c))
lex_error("invalid input character code %1", c);
else {
line += char(c);
if (c == '\n')
break;
}
}
if (line.length() == 0)
return 0;
if (!(line.length() >= 3 && line[0] == '.' && line[1] == 'P'
&& (line[2] == 'S' || line[2] == 'E' || line[2] == 'F')
&& (line.length() == 3 || line[3] == ' ' || line[3] == '\n'
|| compatible_flag))) {
line += '\0';
ptr = line.contents();
return 1;
}
}
}
int file_input::get()
{
if (*ptr != '\0' || read_line())
return (unsigned char)*ptr++;
else
return EOF;
}
int file_input::peek()
{
if (*ptr != '\0' || read_line())
return (unsigned char)*ptr;
else
return EOF;
}
int file_input::get_location(const char **fnp, int *lnp)
{
*fnp = filename;
*lnp = lineno;
return 1;
}
macro_input::macro_input(const char *str)
{
p = s = strsave(str);
}
macro_input::~macro_input()
{
free(s);
}
int macro_input::get()
{
if (p == 0 || *p == '\0')
return EOF;
else
return (unsigned char)*p++;
}
int macro_input::peek()
{
if (p == 0 || *p == '\0')
return EOF;
else
return (unsigned char)*p;
}
char *process_body(const char *body)
{
char *s = strsave(body);
int j = 0;
for (int i = 0; s[i] != '\0'; i++)
if (s[i] == '$' && csdigit(s[i + 1])) {
int n = 0;
int start = i;
i++;
while (csdigit(s[i]))
if (n > MAX_ARG)
i++;
else
n = 10 * n + s[i++] - '0';
if (n > MAX_ARG) {
string arg;
for (int k = start; k < i; k++)
arg += s[k];
lex_error("invalid macro argument number %1", arg.contents());
}
else if (n > 0)
s[j++] = ARG1 + n - 1;
i--;
}
else
s[j++] = s[i];
s[j] = '\0';
return s;
}
argument_macro_input::argument_macro_input(const char *body, int ac, char **av)
: ap(0), argc(ac)
{
for (int i = 0; i < argc; i++)
argv[i] = av[i];
p = s = process_body(body);
}
argument_macro_input::~argument_macro_input()
{
for (int i = 0; i < argc; i++)
free(argv[i]);
free(s);
}
int argument_macro_input::get()
{
if (ap) {
if (*ap != '\0')
return (unsigned char)*ap++;
ap = 0;
}
if (p == 0)
return EOF;
while ((unsigned char)*p >= ARG1
&& (unsigned char)*p <= ARG1 + MAX_ARG - 1) {
int i = (unsigned char)*p++ - ARG1;
if (i < argc && argv[i] != 0 && argv[i][0] != '\0') {
ap = argv[i];
return (unsigned char)*ap++;
}
}
if (*p == '\0')
return EOF;
return (unsigned char)*p++;
}
int argument_macro_input::peek()
{
if (ap) {
if (*ap != '\0')
return (unsigned char)*ap;
ap = 0;
}
if (p == 0)
return EOF;
while ((unsigned char)*p >= ARG1
&& (unsigned char)*p <= ARG1 + MAX_ARG - 1) {
int i = (unsigned char)*p++ - ARG1;
if (i < argc && argv[i] != 0 && argv[i][0] != '\0') {
ap = argv[i];
return (unsigned char)*ap;
}
}
if (*p == '\0')
return EOF;
return (unsigned char)*p;
}
class input_stack {
static input *current_input;
static int bol_flag;
public:
static void push(input *);
static void clear();
static int get_char();
static int peek_char();
static int get_location(const char **fnp, int *lnp);
static void push_back(unsigned char c, int was_bol = 0);
static int bol();
};
input *input_stack::current_input = 0;
int input_stack::bol_flag = 0;
inline int input_stack::bol()
{
return bol_flag;
}
void input_stack::clear()
{
while (current_input != 0) {
input *tem = current_input;
current_input = current_input->next;
delete tem;
}
bol_flag = 1;
}
void input_stack::push(input *in)
{
in->next = current_input;
current_input = in;
}
void lex_init(input *top)
{
input_stack::clear();
input_stack::push(top);
}
void lex_cleanup()
{
while (input_stack::get_char() != EOF)
;
}
int input_stack::get_char()
{
while (current_input != 0) {
int c = current_input->get();
if (c != EOF) {
bol_flag = c == '\n';
return c;
}
// don't pop the top-level input off the stack
if (current_input->next == 0)
return EOF;
input *tem = current_input;
current_input = current_input->next;
delete tem;
}
return EOF;
}
int input_stack::peek_char()
{
while (current_input != 0) {
int c = current_input->peek();
if (c != EOF)
return c;
if (current_input->next == 0)
return EOF;
input *tem = current_input;
current_input = current_input->next;
delete tem;
}
return EOF;
}
class char_input : public input {
int c;
public:
char_input(int);
int get();
int peek();
};
char_input::char_input(int n) : c((unsigned char)n)
{
}
int char_input::get()
{
int n = c;
c = EOF;
return n;
}
int char_input::peek()
{
return c;
}
void input_stack::push_back(unsigned char c, int was_bol)
{
push(new char_input(c));
bol_flag = was_bol;
}
int input_stack::get_location(const char **fnp, int *lnp)
{
for (input *p = current_input; p; p = p->next)
if (p->get_location(fnp, lnp))
return 1;
return 0;
}
string context_buffer;
string token_buffer;
double token_double;
int token_int;
void interpolate_macro_with_args(const char *body)
{
char *argv[MAX_ARG];
int argc = 0;
int ignore = 0;
int i;
for (i = 0; i < MAX_ARG; i++)
argv[i] = 0;
int level = 0;
int c;
enum { NORMAL, IN_STRING, IN_STRING_QUOTED } state = NORMAL;
do {
token_buffer.clear();
for (;;) {
c = input_stack::get_char();
if (c == EOF) {
lex_error("end of input while scanning macro arguments");
break;
}
if (state == NORMAL && level == 0 && (c == ',' || c == ')')) {
if (token_buffer.length() > 0) {
token_buffer += '\0';
if (!ignore) {
if (argc == MAX_ARG) {
lex_warning("only %1 macro arguments supported", MAX_ARG);
ignore = 1;
}
else
argv[argc] = strsave(token_buffer.contents());
}
}
// for 'foo()', argc = 0
if (argc > 0 || c != ')' || i > 0)
if (!ignore)
argc++;
break;
}
token_buffer += char(c);
switch (state) {
case NORMAL:
if (c == '"')
state = IN_STRING;
else if (c == '(')
level++;
else if (c == ')')
level--;
break;
case IN_STRING:
if (c == '"')
state = NORMAL;
else if (c == '\\')
state = IN_STRING_QUOTED;
break;
case IN_STRING_QUOTED:
state = IN_STRING;
break;
}
}
} while (c != ')' && c != EOF);
input_stack::push(new argument_macro_input(body, argc, argv));
}
static int docmp(const char *s1, int n1, const char *s2, int n2)
{
if (n1 < n2) {
int r = memcmp(s1, s2, n1);
return r ? r : -1;
}
else if (n1 > n2) {
int r = memcmp(s1, s2, n2);
return r ? r : 1;
}
else
return memcmp(s1, s2, n1);
}
int lookup_keyword(const char *str, int len)
{
static struct keyword {
const char *name;
int token;
} table[] = {
{ "Here", HERE },
{ "above", ABOVE },
{ "aligned", ALIGNED },
{ "and", AND },
{ "arc", ARC },
{ "arrow", ARROW },
{ "at", AT },
{ "atan2", ATAN2 },
{ "below", BELOW },
{ "between", BETWEEN },
{ "bottom", BOTTOM },
{ "box", BOX },
{ "by", BY },
{ "ccw", CCW },
{ "center", CENTER },
{ "chop", CHOP },
{ "circle", CIRCLE },
{ "color", COLORED },
{ "colored", COLORED },
{ "colour", COLORED },
{ "coloured", COLORED },
{ "command", COMMAND },
{ "copy", COPY },
{ "cos", COS },
{ "cw", CW },
{ "dashed", DASHED },
{ "define", DEFINE },
{ "diam", DIAMETER },
{ "diameter", DIAMETER },
{ "do", DO },
{ "dotted", DOTTED },
{ "down", DOWN },
{ "east", EAST },
{ "ellipse", ELLIPSE },
{ "else", ELSE },
{ "end", END },
{ "exp", EXP },
{ "figname", FIGNAME },
{ "fill", FILL },
{ "filled", FILL },
{ "for", FOR },
{ "from", FROM },
{ "height", HEIGHT },
{ "ht", HEIGHT },
{ "if", IF },
{ "int", INT },
{ "invis", INVISIBLE },
{ "invisible", INVISIBLE },
{ "last", LAST },
{ "left", LEFT },
{ "line", LINE },
{ "ljust", LJUST },
{ "log", LOG },
{ "lower", LOWER },
{ "max", K_MAX },
{ "min", K_MIN },
{ "move", MOVE },
{ "north", NORTH },
{ "of", OF },
{ "outline", OUTLINED },
{ "outlined", OUTLINED },
{ "plot", PLOT },
{ "print", PRINT },
{ "rad", RADIUS },
{ "radius", RADIUS },
{ "rand", RAND },
{ "reset", RESET },
{ "right", RIGHT },
{ "rjust", RJUST },
{ "same", SAME },
{ "sh", SH },
{ "shaded", SHADED },
{ "sin", SIN },
{ "solid", SOLID },
{ "south", SOUTH },
{ "spline", SPLINE },
{ "sprintf", SPRINTF },
{ "sqrt", SQRT },
{ "srand", SRAND },
{ "start", START },
{ "the", THE },
{ "then", THEN },
{ "thick", THICKNESS },
{ "thickness", THICKNESS },
{ "thru", THRU },
{ "to", TO },
{ "top", TOP },
{ "undef", UNDEF },
{ "until", UNTIL },
{ "up", UP },
{ "upper", UPPER },
{ "way", WAY },
{ "west", WEST },
{ "wid", WIDTH },
{ "width", WIDTH },
{ "with", WITH },
{ "xslanted", XSLANTED },
{ "yslanted", YSLANTED },
};
const keyword *start = table;
const keyword *end = table + sizeof(table)/sizeof(table[0]);
while (start < end) {
// start <= target < end
const keyword *mid = start + (end - start)/2;
int cmp = docmp(str, len, mid->name, strlen(mid->name));
if (cmp == 0)
return mid->token;
if (cmp < 0)
end = mid;
else
start = mid + 1;
}
return 0;
}
int get_token_after_dot(int c)
{
// get_token deals with the case where c is a digit
switch (c) {
case 'h':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 't') {
input_stack::get_char();
context_buffer = ".ht";
return DOT_HT;
}
else if (c == 'e') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'i') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'g') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'h') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 't') {
input_stack::get_char();
context_buffer = ".height";
return DOT_HT;
}
input_stack::push_back('h');
}
input_stack::push_back('g');
}
input_stack::push_back('i');
}
input_stack::push_back('e');
}
input_stack::push_back('h');
return '.';
case 'x':
input_stack::get_char();
context_buffer = ".x";
return DOT_X;
case 'y':
input_stack::get_char();
context_buffer = ".y";
return DOT_Y;
case 'c':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'e') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'n') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 't') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'e') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'r') {
input_stack::get_char();
context_buffer = ".center";
return DOT_C;
}
input_stack::push_back('e');
}
input_stack::push_back('t');
}
input_stack::push_back('n');
}
input_stack::push_back('e');
}
context_buffer = ".c";
return DOT_C;
case 'n':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'e') {
input_stack::get_char();
context_buffer = ".ne";
return DOT_NE;
}
else if (c == 'w') {
input_stack::get_char();
context_buffer = ".nw";
return DOT_NW;
}
else {
context_buffer = ".n";
return DOT_N;
}
break;
case 'e':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'n') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'd') {
input_stack::get_char();
context_buffer = ".end";
return DOT_END;
}
input_stack::push_back('n');
context_buffer = ".e";
return DOT_E;
}
context_buffer = ".e";
return DOT_E;
case 'w':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'i') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'd') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 't') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'h') {
input_stack::get_char();
context_buffer = ".width";
return DOT_WID;
}
input_stack::push_back('t');
}
context_buffer = ".wid";
return DOT_WID;
}
input_stack::push_back('i');
}
context_buffer = ".w";
return DOT_W;
case 's':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'e') {
input_stack::get_char();
context_buffer = ".se";
return DOT_SE;
}
else if (c == 'w') {
input_stack::get_char();
context_buffer = ".sw";
return DOT_SW;
}
else {
if (c == 't') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'a') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'r') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 't') {
input_stack::get_char();
context_buffer = ".start";
return DOT_START;
}
input_stack::push_back('r');
}
input_stack::push_back('a');
}
input_stack::push_back('t');
}
context_buffer = ".s";
return DOT_S;
}
break;
case 't':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'o') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'p') {
input_stack::get_char();
context_buffer = ".top";
return DOT_N;
}
input_stack::push_back('o');
}
context_buffer = ".t";
return DOT_N;
case 'l':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'e') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'f') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 't') {
input_stack::get_char();
context_buffer = ".left";
return DOT_W;
}
input_stack::push_back('f');
}
input_stack::push_back('e');
}
context_buffer = ".l";
return DOT_W;
case 'r':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'a') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'd') {
input_stack::get_char();
context_buffer = ".rad";
return DOT_RAD;
}
input_stack::push_back('a');
}
else if (c == 'i') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'g') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'h') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 't') {
input_stack::get_char();
context_buffer = ".right";
return DOT_E;
}
input_stack::push_back('h');
}
input_stack::push_back('g');
}
input_stack::push_back('i');
}
context_buffer = ".r";
return DOT_E;
case 'b':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'o') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 't') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 't') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'o') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'm') {
input_stack::get_char();
context_buffer = ".bottom";
return DOT_S;
}
input_stack::push_back('o');
}
input_stack::push_back('t');
}
context_buffer = ".bot";
return DOT_S;
}
input_stack::push_back('o');
}
context_buffer = ".b";
return DOT_S;
default:
context_buffer = '.';
return '.';
}
}
int get_token(int lookup_flag)
{
context_buffer.clear();
for (;;) {
int n = 0;
int bol = input_stack::bol();
int c = input_stack::get_char();
if (bol && c == command_char) {
token_buffer.clear();
token_buffer += c;
// the newline is not part of the token
for (;;) {
c = input_stack::peek_char();
if (c == EOF || c == '\n')
break;
input_stack::get_char();
token_buffer += char(c);
}
context_buffer = token_buffer;
return COMMAND_LINE;
}
switch (c) {
case EOF:
return EOF;
case ' ':
case '\t':
break;
case '\\':
{
int d = input_stack::peek_char();
if (d != '\n') {
context_buffer = '\\';
return '\\';
}
input_stack::get_char();
break;
}
case '#':
do {
c = input_stack::get_char();
} while (c != '\n' && c != EOF);
if (c == '\n')
context_buffer = '\n';
return c;
case '"':
context_buffer = '"';
token_buffer.clear();
for (;;) {
c = input_stack::get_char();
if (c == '\\') {
context_buffer += '\\';
c = input_stack::peek_char();
if (c == '"') {
input_stack::get_char();
token_buffer += '"';
context_buffer += '"';
}
else
token_buffer += '\\';
}
else if (c == '\n') {
error("newline in string");
break;
}
else if (c == EOF) {
error("missing '\"'");
break;
}
else if (c == '"') {
context_buffer += '"';
break;
}
else {
context_buffer += char(c);
token_buffer += char(c);
}
}
return TEXT;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
int overflow = 0;
n = 0;
for (;;) {
if (n > (INT_MAX - 9)/10) {
overflow = 1;
break;
}
n *= 10;
n += c - '0';
context_buffer += char(c);
c = input_stack::peek_char();
if (c == EOF || !csdigit(c))
break;
c = input_stack::get_char();
}
token_double = n;
if (overflow) {
for (;;) {
token_double *= 10.0;
token_double += c - '0';
context_buffer += char(c);
c = input_stack::peek_char();
if (c == EOF || !csdigit(c))
break;
c = input_stack::get_char();
}
// if somebody asks for 1000000000000th, we will silently
// give them INT_MAXth
double temp = token_double; // work around gas 1.34/sparc bug
if (token_double > INT_MAX)
n = INT_MAX;
else
n = int(temp);
}
}
switch (c) {
case 'i':
case 'I':
context_buffer += char(c);
input_stack::get_char();
return NUMBER;
case '.':
{
context_buffer += '.';
input_stack::get_char();
got_dot:
double factor = 1.0;
for (;;) {
c = input_stack::peek_char();
if (c == EOF || !csdigit(c))
break;
input_stack::get_char();
context_buffer += char(c);
factor /= 10.0;
if (c != '0')
token_double += factor*(c - '0');
}
if (c != 'e' && c != 'E') {
if (c == 'i' || c == 'I') {
context_buffer += char(c);
input_stack::get_char();
}
return NUMBER;
}
}
// fall through
case 'e':
case 'E':
{
int echar = c;
input_stack::get_char();
c = input_stack::peek_char();
int sign = '+';
if (c == '+' || c == '-') {
sign = c;
input_stack::get_char();
c = input_stack::peek_char();
if (c == EOF || !csdigit(c)) {
input_stack::push_back(sign);
input_stack::push_back(echar);
return NUMBER;
}
context_buffer += char(echar);
context_buffer += char(sign);
}
else {
if (c == EOF || !csdigit(c)) {
input_stack::push_back(echar);
return NUMBER;
}
context_buffer += char(echar);
}
input_stack::get_char();
context_buffer += char(c);
n = c - '0';
for (;;) {
c = input_stack::peek_char();
if (c == EOF || !csdigit(c))
break;
input_stack::get_char();
context_buffer += char(c);
n = n*10 + (c - '0');
}
if (sign == '-')
n = -n;
if (c == 'i' || c == 'I') {
context_buffer += char(c);
input_stack::get_char();
}
token_double *= pow(10.0, n);
return NUMBER;
}
case 'n':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'd') {
input_stack::get_char();
token_int = n;
context_buffer += "nd";
return ORDINAL;
}
input_stack::push_back('n');
return NUMBER;
case 'r':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'd') {
input_stack::get_char();
token_int = n;
context_buffer += "rd";
return ORDINAL;
}
input_stack::push_back('r');
return NUMBER;
case 't':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'h') {
input_stack::get_char();
token_int = n;
context_buffer += "th";
return ORDINAL;
}
input_stack::push_back('t');
return NUMBER;
case 's':
input_stack::get_char();
c = input_stack::peek_char();
if (c == 't') {
input_stack::get_char();
token_int = n;
context_buffer += "st";
return ORDINAL;
}
input_stack::push_back('s');
return NUMBER;
default:
return NUMBER;
}
break;
case '\'':
{
c = input_stack::peek_char();
if (c == 't') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == 'h') {
input_stack::get_char();
context_buffer = "'th";
return TH;
}
else
input_stack::push_back('t');
}
context_buffer = "'";
return '\'';
}
case '.':
{
c = input_stack::peek_char();
if (c != EOF && csdigit(c)) {
n = 0;
token_double = 0.0;
context_buffer = '.';
goto got_dot;
}
return get_token_after_dot(c);
}
case '<':
c = input_stack::peek_char();
if (c == '-') {
input_stack::get_char();
c = input_stack::peek_char();
if (c == '>') {
input_stack::get_char();
context_buffer = "<->";
return DOUBLE_ARROW_HEAD;
}
context_buffer = "<-";
return LEFT_ARROW_HEAD;
}
else if (c == '=') {
input_stack::get_char();
context_buffer = "<=";
return LESSEQUAL;
}
context_buffer = "<";
return '<';
case '-':
c = input_stack::peek_char();
if (c == '>') {
input_stack::get_char();
context_buffer = "->";
return RIGHT_ARROW_HEAD;
}
context_buffer = "-";
return '-';
case '!':
c = input_stack::peek_char();
if (c == '=') {
input_stack::get_char();
context_buffer = "!=";
return NOTEQUAL;
}
context_buffer = "!";
return '!';
case '>':
c = input_stack::peek_char();
if (c == '=') {
input_stack::get_char();
context_buffer = ">=";
return GREATEREQUAL;
}
context_buffer = ">";
return '>';
case '=':
c = input_stack::peek_char();
if (c == '=') {
input_stack::get_char();
context_buffer = "==";
return EQUALEQUAL;
}
context_buffer = "=";
return '=';
case '&':
c = input_stack::peek_char();
if (c == '&') {
input_stack::get_char();
context_buffer = "&&";
return ANDAND;
}
context_buffer = "&";
return '&';
case '|':
c = input_stack::peek_char();
if (c == '|') {
input_stack::get_char();
context_buffer = "||";
return OROR;
}
context_buffer = "|";
return '|';
default:
if (c != EOF && csalpha(c)) {
token_buffer.clear();
token_buffer = c;
for (;;) {
c = input_stack::peek_char();
if (c == EOF || (!csalnum(c) && c != '_'))
break;
input_stack::get_char();
token_buffer += char(c);
}
int tok = lookup_keyword(token_buffer.contents(),
token_buffer.length());
if (tok != 0) {
context_buffer = token_buffer;
return tok;
}
char *def = 0;
if (lookup_flag) {
token_buffer += '\0';
def = macro_table.lookup(token_buffer.contents());
token_buffer.set_length(token_buffer.length() - 1);
if (def) {
if (c == '(') {
input_stack::get_char();
interpolate_macro_with_args(def);
}
else
input_stack::push(new macro_input(def));
}
}
if (!def) {
context_buffer = token_buffer;
if (csupper(token_buffer[0]))
return LABEL;
else
return VARIABLE;
}
}
else {
context_buffer = char(c);
return (unsigned char)c;
}
break;
}
}
}
int get_delimited()
{
token_buffer.clear();
int c = input_stack::get_char();
while (c == ' ' || c == '\t' || c == '\n')
c = input_stack::get_char();
if (c == EOF) {
lex_error("missing delimiter");
return 0;
}
context_buffer = char(c);
int had_newline = 0;
int start = c;
int level = 0;
enum { NORMAL, IN_STRING, IN_STRING_QUOTED, DELIM_END } state = NORMAL;
for (;;) {
c = input_stack::get_char();
if (c == EOF) {
lex_error("missing closing delimiter");
return 0;
}
if (c == '\n')
had_newline = 1;
else if (!had_newline)
context_buffer += char(c);
switch (state) {
case NORMAL:
if (start == '{') {
if (c == '{') {
level++;
break;
}
if (c == '}') {
if (--level < 0)
state = DELIM_END;
break;
}
}
else {
if (c == start) {
state = DELIM_END;
break;
}
}
if (c == '"')
state = IN_STRING;
break;
case IN_STRING_QUOTED:
if (c == '\n')
state = NORMAL;
else
state = IN_STRING;
break;
case IN_STRING:
if (c == '"' || c == '\n')
state = NORMAL;
else if (c == '\\')
state = IN_STRING_QUOTED;
break;
case DELIM_END:
// This case it just to shut cfront 2.0 up.
default:
assert(0);
}
if (state == DELIM_END)
break;
token_buffer += c;
}
return 1;
}
void do_define()
{
int t = get_token(0); // do not expand what we are defining
if (t != VARIABLE && t != LABEL) {
lex_error("can only define variable or placename");
return;
}
token_buffer += '\0';
string nm = token_buffer;
const char *name = nm.contents();
if (!get_delimited())
return;
token_buffer += '\0';
macro_table.define(name, strsave(token_buffer.contents()));
}
void do_undef()
{
int t = get_token(0); // do not expand what we are undefining
if (t != VARIABLE && t != LABEL) {
lex_error("can only define variable or placename");
return;
}
token_buffer += '\0';
macro_table.define(token_buffer.contents(), 0);
}
class for_input : public input {
char *var;
char *body;
double from;
double to;
int by_is_multiplicative;
double by;
const char *p;
int done_newline;
public:
for_input(char *, double, double, int, double, char *);
~for_input();
int get();
int peek();
};
for_input::for_input(char *vr, double f, double t,
int bim, double b, char *bd)
: var(vr), body(bd), from(f), to(t), by_is_multiplicative(bim), by(b),
p(body), done_newline(0)
{
}
for_input::~for_input()
{
free(var);
free(body);
}
int for_input::get()
{
if (p == 0)
return EOF;
for (;;) {
if (*p != '\0')
return (unsigned char)*p++;
if (!done_newline) {
done_newline = 1;
return '\n';
}
double val;
if (!lookup_variable(var, &val)) {
lex_error("body of 'for' terminated enclosing block");
return EOF;
}
if (by_is_multiplicative)
val *= by;
else
val += by;
define_variable(var, val);
if ((from <= to && val > to)
|| (from >= to && val < to)) {
p = 0;
return EOF;
}
p = body;
done_newline = 0;
}
}
int for_input::peek()
{
if (p == 0)
return EOF;
if (*p != '\0')
return (unsigned char)*p;
if (!done_newline)
return '\n';
double val;
if (!lookup_variable(var, &val))
return EOF;
if (by_is_multiplicative) {
if (val * by > to)
return EOF;
}
else {
if ((from <= to && val + by > to)
|| (from >= to && val + by < to))
return EOF;
}
if (*body == '\0')
return EOF;
return (unsigned char)*body;
}
void do_for(char *var, double from, double to, int by_is_multiplicative,
double by, char *body)
{
define_variable(var, from);
if ((by_is_multiplicative && by <= 0)
|| (by > 0 && from > to)
|| (by < 0 && from < to))
return;
input_stack::push(new for_input(var, from, to,
by_is_multiplicative, by, body));
}
void do_copy(const char *filename)
{
errno = 0;
FILE *fp = fopen(filename, "r");
if (fp == 0) {
lex_error("can't open '%1': %2", filename, strerror(errno));
return;
}
input_stack::push(new file_input(fp, filename));
}
class copy_thru_input : public input {
int done;
char *body;
char *until;
const char *p;
const char *ap;
int argv[MAX_ARG];
int argc;
string line;
int get_line();
virtual int inget() = 0;
public:
copy_thru_input(const char *b, const char *u);
~copy_thru_input();
int get();
int peek();
};
class copy_file_thru_input : public copy_thru_input {
input *in;
public:
copy_file_thru_input(input *, const char *b, const char *u);
~copy_file_thru_input();
int inget();
};
copy_file_thru_input::copy_file_thru_input(input *i, const char *b,
const char *u)
: copy_thru_input(b, u), in(i)
{
}
copy_file_thru_input::~copy_file_thru_input()
{
delete in;
}
int copy_file_thru_input::inget()
{
if (!in)
return EOF;
else
return in->get();
}
class copy_rest_thru_input : public copy_thru_input {
public:
copy_rest_thru_input(const char *, const char *u);
int inget();
};
copy_rest_thru_input::copy_rest_thru_input(const char *b, const char *u)
: copy_thru_input(b, u)
{
}
int copy_rest_thru_input::inget()
{
while (next != 0) {
int c = next->get();
if (c != EOF)
return c;
if (next->next == 0)
return EOF;
input *tem = next;
next = next->next;
delete tem;
}
return EOF;
}
copy_thru_input::copy_thru_input(const char *b, const char *u)
: done(0)
{
ap = 0;
body = process_body(b);
p = 0;
until = strsave(u);
}
copy_thru_input::~copy_thru_input()
{
delete[] body;
delete[] until;
}
int copy_thru_input::get()
{
if (ap) {
if (*ap != '\0')
return (unsigned char)*ap++;
ap = 0;
}
for (;;) {
if (p == 0) {
if (!get_line())
break;
p = body;
}
if (*p == '\0') {
p = 0;
return '\n';
}
while ((unsigned char)*p >= ARG1
&& (unsigned char)*p <= ARG1 + MAX_ARG - 1) {
int i = (unsigned char)*p++ - ARG1;
if (i < argc && line[argv[i]] != '\0') {
ap = line.contents() + argv[i];
return (unsigned char)*ap++;
}
}
if (*p != '\0')
return (unsigned char)*p++;
}
return EOF;
}
int copy_thru_input::peek()
{
if (ap) {
if (*ap != '\0')
return (unsigned char)*ap;
ap = 0;
}
for (;;) {
if (p == 0) {
if (!get_line())
break;
p = body;
}
if (*p == '\0')
return '\n';
while ((unsigned char)*p >= ARG1
&& (unsigned char)*p <= ARG1 + MAX_ARG - 1) {
int i = (unsigned char)*p++ - ARG1;
if (i < argc && line[argv[i]] != '\0') {
ap = line.contents() + argv[i];
return (unsigned char)*ap;
}
}
if (*p != '\0')
return (unsigned char)*p;
}
return EOF;
}
int copy_thru_input::get_line()
{
if (done)
return 0;
line.clear();
argc = 0;
int c = inget();
for (;;) {
while (c == ' ')
c = inget();
if (c == EOF || c == '\n')
break;
if (argc == MAX_ARG) {
do {
c = inget();
} while (c != '\n' && c != EOF);
break;
}
argv[argc++] = line.length();
do {
line += char(c);
c = inget();
} while (c != ' ' && c != '\n');
line += '\0';
}
if (until != 0 && argc > 0 && strcmp(&line[argv[0]], until) == 0) {
done = 1;
return 0;
}
return argc > 0 || c == '\n';
}
class simple_file_input : public input {
const char *filename;
int lineno;
FILE *fp;
public:
simple_file_input(FILE *, const char *);
~simple_file_input();
int get();
int peek();
int get_location(const char **, int *);
};
simple_file_input::simple_file_input(FILE *p, const char *s)
: filename(s), lineno(1), fp(p)
{
}
simple_file_input::~simple_file_input()
{
// don't delete the filename
fclose(fp);
}
int simple_file_input::get()
{
int c = getc(fp);
while (is_invalid_input_char(c)) {
error("invalid input character code %1", c);
c = getc(fp);
}
if (c == '\n')
lineno++;
return c;
}
int simple_file_input::peek()
{
int c = getc(fp);
while (is_invalid_input_char(c)) {
error("invalid input character code %1", c);
c = getc(fp);
}
if (c != EOF)
ungetc(c, fp);
return c;
}
int simple_file_input::get_location(const char **fnp, int *lnp)
{
*fnp = filename;
*lnp = lineno;
return 1;
}
void copy_file_thru(const char *filename, const char *body, const char *until)
{
errno = 0;
FILE *fp = fopen(filename, "r");
if (fp == 0) {
lex_error("can't open '%1': %2", filename, strerror(errno));
return;
}
input *in = new copy_file_thru_input(new simple_file_input(fp, filename),
body, until);
input_stack::push(in);
}
void copy_rest_thru(const char *body, const char *until)
{
input_stack::push(new copy_rest_thru_input(body, until));
}
void push_body(const char *s)
{
input_stack::push(new char_input('\n'));
input_stack::push(new macro_input(s));
}
int delim_flag = 0;
char *get_thru_arg()
{
int c = input_stack::peek_char();
while (c == ' ') {
input_stack::get_char();
c = input_stack::peek_char();
}
if (c != EOF && csalpha(c)) {
// looks like a macro
input_stack::get_char();
token_buffer = c;
for (;;) {
c = input_stack::peek_char();
if (c == EOF || (!csalnum(c) && c != '_'))
break;
input_stack::get_char();
token_buffer += char(c);
}
context_buffer = token_buffer;
token_buffer += '\0';
char *def = macro_table.lookup(token_buffer.contents());
if (def)
return strsave(def);
// I guess it wasn't a macro after all; so push the macro name back.
// -2 because we added a '\0'
for (int i = token_buffer.length() - 2; i >= 0; i--)
input_stack::push_back(token_buffer[i]);
}
if (get_delimited()) {
token_buffer += '\0';
return strsave(token_buffer.contents());
}
else
return 0;
}
int lookahead_token = -1;
string old_context_buffer;
void do_lookahead()
{
if (lookahead_token == -1) {
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
}
}
int yylex()
{
if (delim_flag) {
assert(lookahead_token == -1);
if (delim_flag == 2) {
if ((yylval.str = get_thru_arg()) != 0)
return DELIMITED;
else
return 0;
}
else {
if (get_delimited()) {
token_buffer += '\0';
yylval.str = strsave(token_buffer.contents());
return DELIMITED;
}
else
return 0;
}
}
for (;;) {
int t;
if (lookahead_token >= 0) {
t = lookahead_token;
lookahead_token = -1;
}
else
t = get_token(1);
switch (t) {
case '\n':
return ';';
case EOF:
return 0;
case DEFINE:
do_define();
break;
case UNDEF:
do_undef();
break;
case ORDINAL:
yylval.n = token_int;
return t;
case NUMBER:
yylval.x = token_double;
return t;
case COMMAND_LINE:
case TEXT:
token_buffer += '\0';
if (!input_stack::get_location(&yylval.lstr.filename,
&yylval.lstr.lineno)) {
yylval.lstr.filename = 0;
yylval.lstr.lineno = -1;
}
yylval.lstr.str = strsave(token_buffer.contents());
return t;
case LABEL:
case VARIABLE:
token_buffer += '\0';
yylval.str = strsave(token_buffer.contents());
return t;
case LEFT:
// change LEFT to LEFT_CORNER when followed by OF
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token == OF)
return LEFT_CORNER;
else
return t;
case RIGHT:
// change RIGHT to RIGHT_CORNER when followed by OF
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token == OF)
return RIGHT_CORNER;
else
return t;
case UPPER:
// recognise UPPER only before LEFT or RIGHT
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token != LEFT && lookahead_token != RIGHT) {
yylval.str = strsave("upper");
return VARIABLE;
}
else
return t;
case LOWER:
// recognise LOWER only before LEFT or RIGHT
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token != LEFT && lookahead_token != RIGHT) {
yylval.str = strsave("lower");
return VARIABLE;
}
else
return t;
case NORTH:
// recognise NORTH only before OF
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token != OF) {
yylval.str = strsave("north");
return VARIABLE;
}
else
return t;
case SOUTH:
// recognise SOUTH only before OF
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token != OF) {
yylval.str = strsave("south");
return VARIABLE;
}
else
return t;
case EAST:
// recognise EAST only before OF
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token != OF) {
yylval.str = strsave("east");
return VARIABLE;
}
else
return t;
case WEST:
// recognise WEST only before OF
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token != OF) {
yylval.str = strsave("west");
return VARIABLE;
}
else
return t;
case TOP:
// recognise TOP only before OF
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token != OF) {
yylval.str = strsave("top");
return VARIABLE;
}
else
return t;
case BOTTOM:
// recognise BOTTOM only before OF
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token != OF) {
yylval.str = strsave("bottom");
return VARIABLE;
}
else
return t;
case CENTER:
// recognise CENTER only before OF
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token != OF) {
yylval.str = strsave("center");
return VARIABLE;
}
else
return t;
case START:
// recognise START only before OF
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token != OF) {
yylval.str = strsave("start");
return VARIABLE;
}
else
return t;
case END:
// recognise END only before OF
old_context_buffer = context_buffer;
lookahead_token = get_token(1);
if (lookahead_token != OF) {
yylval.str = strsave("end");
return VARIABLE;
}
else
return t;
default:
return t;
}
}
}
void lex_error(const char *message,
const errarg &arg1,
const errarg &arg2,
const errarg &arg3)
{
const char *filename;
int lineno;
if (!input_stack::get_location(&filename, &lineno))
error(message, arg1, arg2, arg3);
else
error_with_file_and_line(filename, lineno, message, arg1, arg2, arg3);
}
void lex_warning(const char *message,
const errarg &arg1,
const errarg &arg2,
const errarg &arg3)
{
const char *filename;
int lineno;
if (!input_stack::get_location(&filename, &lineno))
warning(message, arg1, arg2, arg3);
else
warning_with_file_and_line(filename, lineno, message, arg1, arg2, arg3);
}
void yyerror(const char *s)
{
const char *filename;
int lineno;
const char *context = 0;
if (lookahead_token == -1) {
if (context_buffer.length() > 0) {
context_buffer += '\0';
context = context_buffer.contents();
}
}
else {
if (old_context_buffer.length() > 0) {
old_context_buffer += '\0';
context = old_context_buffer.contents();
}
}
if (!input_stack::get_location(&filename, &lineno)) {
if (context) {
if (context[0] == '\n' && context[1] == '\0')
error("%1 before newline", s);
else
error("%1 before '%2'", s, context);
}
else
error("%1 at end of picture", s);
}
else {
if (context) {
if (context[0] == '\n' && context[1] == '\0')
error_with_file_and_line(filename, lineno, "%1 before newline", s);
else
error_with_file_and_line(filename, lineno, "%1 before '%2'",
s, context);
}
else
error_with_file_and_line(filename, lineno, "%1 at end of picture", s);
}
}