#!/usr/bin/env python3
# SPDX-License-Identifier: BSD-2-Clause

from __future__ import annotations

import os
import re
import sys
from typing import Any, List, Optional, TextIO

# Prefer a local PLY checkout if present.
Token = Any

_script_dir = os.path.dirname(os.path.abspath(__file__))
_local_ply_dir = os.path.join(_script_dir, "ply")
_local_ply_src = os.path.join(_local_ply_dir, "src")
if os.path.isdir(_local_ply_dir) and os.path.isdir(_local_ply_src):
    sys.path.insert(0, _local_ply_src)

ctssdirs = "../libctss"

# The head of Alfred E. Neumann from 1960, to be emitted on excessive compiler
# errors.
alfie = """
                            MADMADMAD
                     MADMADMADMADMADMADMADMA
                 MADMADMADMADM6100(YADMADMADMADM
               MADMADMADMADMADMADMADMADMADMADMADMAD
             MADMADMADMADMADMADMADMADMADMADMADMADMADM
            MADMADMADMADMADMADMADMADMADMADMADMADMADMAD
           MADMADMADMADMADMADMADMADMADMADMADMADMADMADMAD
           MADMADMADMA   DMADMADMADMADMADMADMADMADMADMAD
         MADMADM M M           MADMADMADMADMADMADMADMADMA
        MADMADMA                                 MADMADMAD
        MADMADMA                                     MADMA
        MADMADM                                      MADMA
        MADMA                                        MADMA
        MADMA      MMMMMMMM                          MADMA
        MADMA     M        M                         MADMA
       MADMA       ......          MMMMMMM           MADMA
  *****MADM        . (U) .          ......           MADM
 *     *MA          .....          . (U) .           MAD
*   ... *                    .       ....            M*****
*  .    *                    .     .                **     *
*   .    *                 ..        .              *  ..  *
 *   .    *                ....   ...   .          *   . . *
  *   .    *        .          ...     ...         *    .  *
   **      *      ..                    ..        *   ..  **
     *     *     .. .                            *   .   *
      ******         ...................        *      **
            ***       .. ..M M M   M..         * *   **
               **       .. ..........         *   ***
                 *        .........        ***
                --*                     ***
              ---****             ******  *
           ----  *   ****    *****        *
         -------  *      ****            *
       ----------  **                   * --
     -------------   **                *   ----
   ----------------    *              *     -----


                       ******************
                       * WHAT, ME WORRY *
                       ******************
"""
alfiemin = 3

#
# Format Processor
#

formatter = """
void ProcessFmt(FmtVar *fmtvar, char *cfmt, char *madfmt, int read, int first)
{
   char *bp, *ep;
   int genccode;
   char ch;
   char pfmt[256];
   char lfmt[256];

   *cfmt = 0;
   genccode = first;
   if (first) {
      strcpy(ErrFmt, madfmt);
   }

   bp = madfmt;
   /* Substitute all FORMAT VARIABLES before processing */
   if ((ep = strchr(bp, '\\'')) != NULL) {
      char *pf;
      int j;

      pfmt[0] = 0;
      pf = pfmt;
      while (ep) {
         strncpy(pf, bp, ep-bp);
         pf += (ep-bp);
         *pf = 0;
         bp = ep + 1;
         if ((ep = strchr(bp, '\\'')) != NULL) {
            strncpy(lfmt, bp, ep-bp);
            lfmt[ep-bp] = 0;
            for (j = 0; fmtvar[j].loc != NULL; j++) {
               if (!strcmp(fmtvar[j].var, lfmt)) {
                  int count;
                  char num[10];
                  count = *fmtvar[j].loc;
                  sprintf(num, "%d", count);
                  strcpy(pf, num);
                  pf += strlen(num);
                  break;
               }
            }
            if (fmtvar[j].loc == NULL) {
               fprintf(stderr, "Undeclared FORMAT VARIABLE: %s\\n", lfmt);
               exit(1);
            }
            bp = ep + 1;
            ep = strchr(bp, '\\'');
         } else {
            fprintf(stderr, "Unterminated FORMAT VARIABLE: %-7.7s\\n", bp-1);
            exit(1);
         }
      }
      strcpy(pf, bp);
      madfmt = pfmt;
   }
   /* Process FORMAT */
   while (*madfmt && *madfmt != '*') {
      int prefix;
      int count;

      ch = *madfmt++;
      if (ch == '/') {
         strcat(cfmt, "\\n");
         genccode = True;
         continue;
      }
      else if (isspace(ch) || ch == ',') {
         continue;
      }
      else if (ch == 'S') {
         count = 0;
         if (isdigit(*madfmt)) {
            while (*madfmt && isdigit(*madfmt)) {
               count = (count * 10) + (*madfmt++ - '0');
            }
         }
         while (count) {
            strcat (cfmt, " ");
            count--;
         }
         continue;
      }
      if (isdigit(ch)) {
         prefix = 0;
         madfmt--;
         while (*madfmt && isdigit(*madfmt)) {
            prefix = (prefix * 10) + (*madfmt++ - '0');
         }
         ch = *madfmt++;
      } else {
         prefix = 1;
      }
      if (ch == '(') {
         bp = madfmt;
         if ((ep = strchr(madfmt, ')')) != NULL) {
            strncpy(lfmt, bp, ep-bp);
            lfmt[ep-bp] = 0;
            for (; prefix; prefix--) {
               ProcessFmt(fmtvar, &cfmt[strlen(cfmt)], lfmt, read, False);
            }
            madfmt = ep + 1;
         }
      }
      else if (ch == 'K' || ch == 'I') {
         count = 0;
         while (*madfmt && isdigit(*madfmt)) {
            count = (count * 10) + (*madfmt++ - '0');
         }
         if (read) {
            strcpy(lfmt, (ch == 'K' ? "%lo":"%ld"));
         } else {
            sprintf(lfmt, "%%%dll%s", count, (ch == 'K' ? "o":"d"));
         }
         if (prefix > 1) strcat (lfmt, " ");
         for (; prefix; prefix--) {
            strcat(&cfmt[strlen(cfmt)], lfmt);
         }
      }
      else if (ch == 'C') {
         count = 0;
         while (*madfmt && isdigit(*madfmt)) {
            count = (count * 10) + (*madfmt++ - '0');
         }
         sprintf(lfmt, "%%%ds", prefix * count);
         strcat(&cfmt[strlen(cfmt)], lfmt);
      }
      else if (ch == 'F' || ch == 'E') {
         int precision;
         count = 0;
         while (*madfmt && isdigit(*madfmt)) {
            count = (count * 10) + (*madfmt++ - '0');
         }
         precision = 0;
         if (*madfmt == '.') {
            madfmt++;
            while (*madfmt && isdigit(*madfmt)) {
               precision = (precision * 10) + (*madfmt++ - '0');
            }
         }
         if (read) {
            strcpy(lfmt, (ch == 'F' ? "%lf":"%le"));
         } else {
            sprintf(lfmt, "%%%d.%d%s", count, precision, (ch == 'F' ? "f":"e"));
         }
         if (prefix > 1) strcat (lfmt, " ");
         for (; prefix; prefix--) {
            strcat(&cfmt[strlen(cfmt)], lfmt);
         }
      }
      else if (ch == 'H') {
         if (genccode) {
            genccode = False;
            prefix--;
            switch (*madfmt++) {
            case ' ':
               break;
            case '+':
               strcat(cfmt, "\\r");
               break;
            case '0':
               strcat(cfmt, "\\n");
               break;
            case '1':
               strcat(cfmt, "\\f");
               break;
            case '2':
               strcat(cfmt, "\\v\\v");
               break;
            case '3':
            case '4':
               break;
            default:
               prefix++;
               madfmt--;
            }
         }
         for (; prefix && *madfmt; prefix--) {
            lfmt[0] = *madfmt++;
            lfmt[1] = 0;
            strcat(&cfmt[strlen(cfmt)], lfmt);
         }
      } else {
         fprintf(stderr, "Unsupported FORMAT specifier: %c\\n", ch);
         exit(1);
      }
   }
   if (first) {
      if (!*madfmt) {
         fprintf(stderr, "Unterminated FORMAT: %s\\n", ErrFmt);
         exit(1);
      }
      if (!read) {
         strcat(cfmt, "\\n");
      }
   }
}

"""

#
# PackStr routine
#

packstr = """
static int64_t PackStr (const char *s)
{
   int64_t r;
   int ch;

   r = 0x202020202020L;
   for (;*s;s++) {
      ch = *s;
      if (islower(ch)) ch = toupper(ch);
      r = (r << 8) | ch;
   }
   return r & 0xFFFFFFFFFFFFL;
}
"""

#
# Cursor routines
#

checkrecursor = """
void CheckRecursorPop(int cnt)
{
   if (recursorlen < 0)
      return;
   if ((recursorcnt - cnt) < 0) {
      fputs("RESTORE DATA underflow\\n", stderr);
      exit(1);
   }
   recursorcnt -= cnt;
}

void CheckRecursorPush(int cnt)
{
   if (recursorlen < 0)
      return;
   if ((recursorcnt + cnt) > recursorlen) {
      fputs("SAVE DATA overflow\\n", stderr);
      exit(1);
   }
   recursorcnt += cnt;
}

"""

#
# TAPE routines
#

taperoutines = """
FILE *TapeOpen(FILE *tfd, char *mode, int64_t lun)
{
   FILE *fd = NULL;
   char filename[32];

   if (tfd != NULL) {
      return tfd;
   }
   sprintf(filename, "Tape_%ld.dat", lun);
   if ((fd = fopen(filename, mode)) == NULL) {
      fprintf(stderr, "Can't open TAPE file: %s(%s): %s\\n",
               filename, mode, strerror(errno));
      exit(1);
   }
   return fd;
}

FILE *TapeClose(FILE *tfd, int64_t lun, int unload)
{
   if (tfd == NULL) {
      return NULL;
   }
   if (fclose(tfd) < 0) {
      fprintf(stderr, "Can't %s TAPE: Tape_%ld: %s\\n",
               unload ? "unload" : "write EOF", lun, strerror(errno));
      exit(1);
   }
   return NULL;
}

void TapeRewind(FILE *tfd, int64_t lun, long *loc)
{
   if (tfd == NULL) {
      return;
   }
   if (fseek(tfd, 0, SEEK_SET) < 0) {
      fprintf(stderr, "Can't rewind TAPE: Tape_%ld: %s\\n",
               lun, strerror(errno));
      exit(1);
   }
   *loc = 0;
}

void TapeBackspace(FILE *tfd, int64_t lun, long *loc)
{
   if (fseek(tfd, *loc, SEEK_SET) < 0) {
      fprintf(stderr, "Can't BACKSPACE TAPE: Tape_%ld: %s\\n",
               lun, strerror(errno));
      exit(1);
   }
}

void TapeReadBinary(TapeVector *vecs, FILE *tfd, int64_t lun, int cnt,
                     long *loc, int64_t *eof)
{
   int i;

   *loc = ftell(tfd);
   for (i = 0; i < cnt; i++) {
      if (fread(vecs[i].ptr, 1, vecs[i].len, tfd) != vecs[i].len) {
         if (feof(tfd)) {
            if (eof != NULL) {
               *eof = 1L;
               return;
            }
         } else {
            fprintf(stderr, "Can't READ BINARY TAPE: Tape_%ld: %s\\n",
                     lun, strerror(ferror(tfd)));
         }
         exit(1);
      }
   }
}

void TapeWriteBinary(TapeVector *vecs, FILE *tfd, int64_t lun, int cnt,
                      long *loc)
{
   int i;

   *loc = ftell(tfd);
   for (i = 0; i < cnt; i++) {
      if (fwrite(vecs[i].ptr, 1, vecs[i].len, tfd) != vecs[i].len) {
         if (!feof(tfd)) 
            fprintf(stderr, "Can't WRITE BINARY TAPE: Tape_%ld: %s\\n",
                     lun, strerror(ferror(tfd)));
         exit(1);
      }
   }
}

void seteof(int64_t *lbl)
{
   *lbl = 0LL;
}

"""

#
# READ AND PRINT routines
#

readprintdata = r"""
struct datamap_t {
     char *name;
     int type;
#define INT   0
#define FLOAT 1
#define BOOL  2
     union val_u {
         int64_t *intp;
         double *floatp;
         bool *boolp;
     } val;
     bool used;
};

void ReadPrintData(struct datamap_t *map, int readonly)
{
    struct datamap_t *sp;
    int done = False;
    int c, state = 0;
    char namebuf[7], *np;
    char valbuf[32], *fp;

    while (!done && (c = getchar()) != EOF) {
        switch (state) {
        case 0:        /* initial state */
            if (isspace(c))
                continue;
            else if (isalnum(c)) {
                state = 1;
                ungetc(c, stdin);
                np = namebuf;
                sp = NULL;
                continue;
            } else {
                fputs("%(target)s: data format error while awaiting variable.\n", stderr);
                exit(1);
            }
            break;
        case 1:        /* collecting variable name */
            if (isalnum(c) || c == '_') {
                *np++ = tolower(c);
                continue;
            } else {
                bool foundit = False;
                *np++ = (char)0;
                for (sp = map; sp->name != NULL; sp++)
                    if (strcmp(sp->name, namebuf) == 0) {
                        foundit = True;
                        break;
                    }
                if (foundit) {
                    state = 2;
                    sp->used = True;
                    ungetc(c, stdin);
                    continue;
                } else {
                    fprintf(stderr, "%(target)s: unknown variable name %%s in data.\n", namebuf);
                    exit(1);
                }
            }
            break;
        case 2:        /* awaiting delimiter */
            if (isspace(c) || c == '=')
                continue;
            else {
                state = 3;
                ungetc(c, stdin);
                fp = valbuf;
                continue;
            }
            break;
        case 3:        /* awaiting value */
            if (isdigit(c) || c == '.' || c == '+' || c == '-') {
                *fp++ = c;
                continue;
            } if (isspace(c) || c == ',' || c == '*') {
                *fp++ = (char)0;
                switch (sp->type) {
                    case INT:
                        sscanf(valbuf, "%%ld", (long *)sp->val.intp);
                        break;
                    case FLOAT:
                        sscanf(valbuf, "%%lf", sp->val.floatp);
                        break;
                    case BOOL:
                        sscanf(valbuf, "%%dB\n", (int *)sp->val.boolp);
                        break;
                    }
                state = 0;
                if (c == '*') {
                    done = True;
                }
                continue;
            } else {
                fputs("%(target)s: unexpected character in literal.\n", stderr);
                exit(1);
            }
            break;
        }
    }
    if (c == EOF) {
       exit(0);
    }

    if (!readonly)
      for (sp = map; sp->name != NULL; sp++)
        if (sp->used)
            switch (sp->type) {
                case INT:
                    printf("%%s = %%ld\n", sp->name, (long)sp->val.intp[0]);
                    break;
                case FLOAT:
                    printf("%%s = %%f\n", sp->name, sp->val.floatp[0]);
                    break;
                case BOOL:
                    printf("%%s = %%dB\n", sp->name, (int)sp->val.boolp[0]);
                    break;
                }
}
"""

reserved = (
    "BOOLEAN",
    "CONDITIONAL",
    "CONTINUE",
    "DATA",
    "DIMENSION",
    "BACKSPACE_RECORD_OF_TAPE",
    "END_OF_FILE_TAPE",
    "END",
    "ENTRY",
    "ERASABLE",
    "ERROR_RETURN",
    "EXECUTE",
    "EXTERNAL",
    "FLOATING_POINT",
    "FOR",
    "FORMAT_VARIABLE",
    "FUNCTION",
    "FUNCTION_RETURN",
    "INTEGER",
    "INTERNAL",
    "IS",
    "MODE",
    "NORMAL",
    "OF",
    "OR",
    "OTHERWISE",
    "PARAMETER",
    "PAUSE",
    "PRINT_COMMENT",
    "PRINT_FORMAT",
    "PRINT_ONLINE_FORMAT",
    "PRINT_RESULTS",
    "PROGRAM",
    "PROGRAM_COMMON",
    "READ_DATA",
    "READ_AND_PRINT_DATA",
    "READ_BCD_TAPE",
    "READ_BINARY_TAPE",
    "READ_FORMAT",
    "REWIND_TAPE",
    "RESTORE",
    "RESTORE_RETURN",
    "SAVE",
    "SAVE_RETURN",
    "SET_LIST_TO",
    "STATEMENT_LABEL",
    "THROUGH",
    "TO",
    "TRANSFER",
    "VALUES",
    "VECTOR",
    "WHENEVER",
    "WRITE_BCD_TAPE",
    "WRITE_BINARY_TAPE",
    "UNLOAD_TAPE",
    # COMMENT is a pseudo-keyword generated early in compilation.
    "INSERT_FILE",
    "COMMENT",
    "DOTRANGE",
)

tokens = reserved + (
    "EOS",
    "COMMA",
    "VBAR",
    "NAME",
    "INTNUM",
    "OCTNUM",
    "FLOATNUM",
    "BOOLNUM",
    "STRING",
    "FUNCALL",
    "LPAREN",
    "RPAREN",
    "PLUS",
    "MINUS",
    "TIMES",
    "DIVIDE",
    "MOD",
    "EQUALS",
    "NOT",
    "AND",
    "THEN",
    "EQV",  # See above for OR
    "EQ",
    "NE",
    "LT",
    "LE",
    "GT",
    "GE",
    "ABS",
    "POW",
    "RS",
    "LS",
    "V",
    "EV",
    "A",
)

logops = (
    "NOT",
    "OR",
    "AND",
    "THEN",
    "EQV",
    "EQ",
    "NE",
    "LT",
    "LE",
    "GT",
    "GE",
)

bitops = ("RS", "LS", "V", "EV", "A")

# Tokens

t_EOS = r"eos"
t_COMMA = r"\,"
t_ABS = r"\.ABS\."
t_POW = r"\.P\."
t_NOT = r"\.NOT\."
t_OR = r"\.OR\."
t_AND = r"\.AND\."
t_THEN = r"\.THEN\."
t_EQV = r"\.EQV\."
t_LT = r"\.L\."
t_LE = r"\.LE\."
t_GT = r"\.G\."
t_GE = r"\.GE\."
t_EQ = r"\.E\."
t_NE = r"\.NE\."
t_RS = r"\.RS\."
t_LS = r"\.LS\."
t_V = r"\.V\."
t_EV = r"\.EV\."
t_A = r"\.A\."
t_MOD = r"\.MOD\."
t_PLUS = r"\+"
t_MINUS = r"-"
t_TIMES = r"\*"
t_DIVIDE = r"/"
t_VBAR = r"\|"
t_EQUALS = r"="
t_LPAREN = r"\("
t_RPAREN = r"\)"
t_DOTRANGE = r"\.\.\."

reserved_map = {}

for r in reserved:
    reserved_map[r] = r


def t_STRING(t: Token) -> Optional[Token]:
    r"\$[^$]*\$"
    t.value = t.value[1:-1]
    t.value = re.sub(r"\n\s*", "", t.value)
    return t


def t_FUNCALL(t: Token) -> Optional[Token]:
    r"[A-Z][A-Z0-9]*\.(?=\s*(\(|eos|,|\)))"
    t.type = reserved_map.get(t.value, "FUNCALL")
    t.value = t.value.lower()
    return t


def t_NAME(t: Token) -> Optional[Token]:
    r"[A-Z][A-Z0-9_]*"
    t.type = reserved_map.get(t.value, "NAME")
    t.value = t.value.lower()
    return t


def t_FLOATNUM(t: Token) -> Optional[Token]:
    r"(\d+\.\d*|\.\d+)"
    if t.value[-1] == ".":
        t.value += "0"
    if t.value[0] == ".":
        t.value = "0" + t.value
    return t


def t_BOOLNUM(t: Token) -> Optional[Token]:
    r"[01]B"
    t.value = int(t.value[0]) == 1
    return t


def t_OCTNUM(t: Token) -> Optional[Token]:
    r"([0-7][0-7]*K)"
    t.value = "0" + t.value[0 : len(t.value) - 1]
    return t


def t_INTNUM(t: Token) -> Optional[Token]:
    r"\d+"
    # Leading zeros flip us into octal in C, therefore strip them.
    while len(t.value) > 1 and t.value[0] == "0":
        t.value = t.value[1:]
    return t


# Define a rule so we can track line numbers
def t_newline(t: Token) -> Optional[Token]:
    r"\n+"
    t.lexer.lineno += len(t.value)


t_ignore = " \t"


def t_error(t: Token) -> Optional[Token]:
    global sourceline
    sourceline = lexer.lineno
    nonfatal("Illegal character %s" % repr(t.value[0]))
    t.lexer.skip(1)


# Build the lexer
from ply import lex
from ply import yacc

lexer = lex.lex()

# Parsing rules

precedence = (
    ("left", "EQV"),
    ("left", "THEN"),
    ("left", "OR"),
    ("left", "AND"),
    ("right", "NOT"),
    ("left", "EQ", "NE", "LT", "LE", "GT", "GE"),
    ("left", "PLUS", "MINUS"),
    ("left", "TIMES", "DIVIDE", "MOD"),
    ("right", "UMINUS"),
    ("left", "POW"),
    ("left", "V", "EV"),
    ("left", "A"),
    ("left", "LS", "RS"),
    ("right", "ABS", "UPLUS"),
)

refcount = {}  # Reference-counts of variables
sourcefile = ""  # Current source file for error messages
sourceline = 0  # Current source line for error messages
deck = []  # List of tuples generated by parsing
targets = {}  # Dictionary of targets of TRANSFERs
stmtlabels = {}  # Dictionary of statment labels.
forlabels = {}  # Dictionary of for target labels.
headers = ["stdint"]  # Required .h file basenames
dimensions = {}  # Dictionary of array depths
set_lists = []  # Dictionary of SET LIST
arraydepths = {}  # Dictionary of array dimensions
returntypes = {"main.": "int"}  # Dictionary of function returntypes
funcargs = {}  # Current function arg list
funcname = ""  # Current function name
vecvalues = {}  # Dictionary of VECTOR VALUES variables
common = {}  # Dictionary of PROGRAM COMMON variables
format = {}  # Dictionary of format types
parameters = {}  # Dictionary of PARAMETERs
formatvars = {}  # Dictionary of FORMAT VARIABLES
hasmain = False  # Does this program have a main line?
packstrings = True  # Pack strings into integer when True
emitpackstr = False  # emit PackStr function
emitscanf = False  # Does this program read using scanf
emittapeio = False  # Does this program read/write TAPE
termbraceemit = 0  # Have we emmited an terminal brace?
emitformatter = False  # Program has formats emit function
emitreadprint = False  # Program has READ AND PRINT DATA
emitcursor = False  # External function uses SAVE/RESTORE
emitchkcursor = False  # Program uses SET LIST TO
ctssmode = False  # Generate CTSS mode code
emitargs = False  # Emit function 'execute' call args
internalfunctions = []  # Dictionary of internal functions
executefunctions = []  # Dictionary of execute functions
typedict = {}  # Types of globals
normalmode = "default"  # Default type as set by NORMAL statement
dummycount = 0  # Count of FOR VALUES statements
assigntargets = {}  # Assignment targets
switchtables = {}  # Switch table dictionary
tapeluns = {}  # TAPE I/O luns
maxtapevectors = 20  # Maximum tape vectors in TapeVectors
tapeeofvector = ""  # TAPE EOF vector, set by seteof() reference.

# Program level


def p_program(t: Token) -> None:
    """program : card
    | program card"""


def p_card(t: Token) -> None:
    "card : VBAR statement EOS"
    deck.append((sourcefile, lexer.lineno, None, t[2]))


def p_labeled_card(t: Token) -> None:
    "card : NAME VBAR statement EOS"
    deck.append((sourcefile, lexer.lineno, t[1], t[3]))


def p_labeled_card1(t: Token) -> None:
    "card : NAME LPAREN INTNUM RPAREN VBAR statement EOS"
    label = t[1] + "_" + t[3]
    deck.append((sourcefile, lexer.lineno, label, t[6]))
    if t[1] not in switchtables:
        switchtables[t[1]] = [("variable", label)]
    else:
        switchtables[t[1]] += [("variable", label)]
    targets[label] = targets.get(label, 0) + 1


def p_empty_card(t: Token) -> None:
    "card : VBAR EOS"
    pass


# Statement level


## BOOLEAN
def p_boolean(t: Token) -> None:
    "statement : BOOLEAN boollist"
    t[0] = ("boolean",) + tuple(t[2])


def p_boollist(t: Token) -> None:
    "boollist : NAME"
    t[0] = [t[1]]


def p_boollist_continue(t: Token) -> None:
    "boollist : boollist COMMA NAME"
    t[0] = t[1] + [t[3]]


## CONTINUE
def p_statement_continue(t: Token) -> None:
    "statement : CONTINUE"
    t[0] = ("continue",)


## PROGRAM_COMMON
def p_statement_common(t: Token) -> None:
    "statement : PROGRAM_COMMON common_list"
    t[0] = ("common", t[2])


## ERASABLE
def p_statement_erasable(t: Token) -> None:
    "statement : ERASABLE common_list"
    t[0] = ("common", t[2])


def p_common_list(t: Token) -> None:
    "common_list : comspec"
    t[0] = [t[1]]


def p_common_list_continue(t: Token) -> None:
    "common_list : common_list COMMA comspec"
    t[0] = t[1] + [t[3]]


def p_comspec(t: Token) -> None:
    "comspec : NAME LPAREN INTNUM RPAREN"
    t[0] = (t[1], repr(int(t[3]) + 1))
    typedict[t[1]] = normalmode


def p_comspec_0(t: Token) -> None:
    "comspec : NAME"
    t[0] = (t[1], "0")
    typedict[t[1]] = normalmode


## PARAMETER
def p_statement_parameter(t: Token) -> None:
    "statement : PARAMETER parameter_list"
    t[0] = ("parameter", t[2])


def p_parameter_list(t: Token) -> None:
    "parameter_list : parmspec"
    t[0] = [t[1]]


def p_parameter_list_continue(t: Token) -> None:
    "parameter_list : parameter_list COMMA parmspec"
    t[0] = t[1] + [t[3]]


def p_parmspec(t: Token) -> None:
    "parmspec : NAME LPAREN NAME RPAREN"
    parameters[t[1]] = t[3]
    t[0] = (t[1], t[3])


def p_parmspec_0(t: Token) -> None:
    "parmspec : NAME LPAREN INTNUM RPAREN"
    parameters[t[1]] = t[3]
    t[0] = (t[1], t[3])


def p_parmspec_1(t: Token) -> None:
    "parmspec : NAME LPAREN FLOATNUM RPAREN"
    parameters[t[1]] = t[3]
    t[0] = (t[1], t[3])


## FORMAT_VARIABLE
def p_statement_format_variable(t: Token) -> None:
    "statement : FORMAT_VARIABLE format_variable_list"
    t[0] = ("format_variable", t[2])


def p_format_variable_list(t: Token) -> None:
    "format_variable_list : formatspec"
    t[0] = [t[1]]


def p_format_variable_list_continue(t: Token) -> None:
    "format_variable_list : format_variable_list COMMA formatspec"
    t[0] = t[1] + [t[3]]


def p_formatspec(t: Token) -> None:
    "formatspec : NAME"
    formatvars[t[1]] = t[1]
    typedict[t[1]] = "int64_t"
    t[0] = t[1]


## DIMENSION
def p_statement_dimension(t: Token) -> None:
    "statement : DIMENSION dimension_list"
    t[0] = ("dimension", t[2])


def p_dimension_list(t: Token) -> None:
    "dimension_list : dimspec"
    t[0] = [t[1]]


def p_dimension_list_continue(t: Token) -> None:
    "dimension_list : dimension_list COMMA dimspec"
    t[0] = t[1] + [t[3]]


# Screwy MAD semantics, all dimensions are bumped up 1.
# Probably designed as a cheap way to prevent fencepost errors.
def p_dimspec_0(t: Token) -> None:
    "dimspec : NAME LPAREN INTNUM RPAREN"
    t[0] = (t[1], repr(int(t[3]) + 1))
    typedict[t[1]] = normalmode


def p_dimspec_1(t: Token) -> None:
    "dimspec : NAME LPAREN INTNUM COMMA NAME RPAREN"
    t[0] = (t[1], repr(int(t[3]) + 1), t[5])
    refcount[t[5]] = refcount.get(t[5], 0) + 1
    typedict[t[5]] = "int64_t"


def p_dimspec_2(t: Token) -> None:
    "dimspec : NAME LPAREN INTNUM COMMA NAME LPAREN INTNUM RPAREN RPAREN"
    t[0] = (t[1], repr(int(t[3]) + 1), t[5], t[7])
    refcount[t[5]] = refcount.get(t[5], 0) + 1
    typedict[t[5]] = "int64_t"


## END
def p_end_of_program(t: Token) -> None:
    "statement : END OF PROGRAM"
    t[0] = ("end_program",)


def p_end_of_conditional(t: Token) -> None:
    "statement : END OF CONDITIONAL"
    t[0] = ("end_conditional",)


def p_end_of_functions(t: Token) -> None:
    "statement : END OF FUNCTION"
    t[0] = ("end_function",)


## ENTRY
def p_entry(t: Token) -> None:
    "statement : ENTRY TO FUNCALL"
    t[0] = ("entry", t[3])


## ERROR_RETURN
def p_error_return(t: Token) -> None:
    "statement : ERROR_RETURN"
    t[0] = ("error_return",)


## EXECUTE
def p_execute(t: Token) -> None:
    "statement : EXECUTE FUNCALL application"
    found = False
    for i in range(len(executefunctions)):
        (currentfunc, args) = executefunctions[i]
        if currentfunc == t[2]:
            found = True
            break
    if not found:
        executefunctions.append((t[2], t[3]))
    t[0] = ("execute", ("apply", t[2], t[3]))


def p_execute_noargs(t: Token) -> None:
    "statement : EXECUTE FUNCALL"
    found = False
    for i in range(len(executefunctions)):
        (currentfunc, args) = executefunctions[i]
        if currentfunc == t[2]:
            found = True
            break
    if not found:
        executefunctions.append((t[2], []))
    t[0] = ("execute", ("apply", t[2], []))


## Bare procedure call
def p_procedure(t: Token) -> None:
    "statement : FUNCALL application"
    found = False
    for i in range(len(executefunctions)):
        (currentfunc, args) = executefunctions[i]
        if currentfunc == t[1]:
            found = True
            break
    if not found:
        executefunctions.append((t[1], t[2]))
    t[0] = ("execute", ("apply", t[1], t[2]))


def p_procedure_noargs(t: Token) -> None:
    "statement : FUNCALL"
    found = False
    for i in range(len(executefunctions)):
        (currentfunc, args) = executefunctions[i]
        if currentfunc == t[1]:
            found = True
            break
    if not found:
        executefunctions.append((t[1], []))
    t[0] = ("execute", ("apply", t[1], []))


## EXTERNAL
def p_external_call(t: Token) -> None:
    "statement : EXTERNAL FUNCALL application"
    t[0] = ("external_call", t[2], tuple(t[3]))


## EXTERNAL FUNCTION
def p_external_function(t: Token) -> None:
    "statement : EXTERNAL FUNCTION application"
    t[0] = ("external_function", tuple(t[3]))


def p_external_function0(t: Token) -> None:
    "statement : EXTERNAL FUNCTION"
    t[0] = ("external_function", ())


## FUNCTION_RETURN
def p_function_return(t: Token) -> None:
    "statement : FUNCTION_RETURN expression"
    t[0] = ("return", t[2])


def p_function_return_void(t: Token) -> None:
    "statement : FUNCTION_RETURN"
    t[0] = ("return",)


## FLOATING_POINT
def p_float(t: Token) -> None:
    "statement : FLOATING_POINT floatlist"
    t[0] = ("floating_point",) + tuple(t[2])


def p_floatlist(t: Token) -> None:
    "floatlist : NAME"
    t[0] = [t[1]]


def p_floatlist_continue(t: Token) -> None:
    "floatlist : floatlist COMMA NAME"
    t[0] = t[1] + [t[3]]


## INTEGER
def p_integer(t: Token) -> None:
    "statement : INTEGER intlist"
    t[0] = ("integer",) + tuple(t[2])


def p_intlist(t: Token) -> None:
    "intlist : NAME"
    t[0] = [t[1]]


def p_intlist_continue(t: Token) -> None:
    "intlist : intlist COMMA NAME"
    t[0] = t[1] + [t[3]]


## INTERNAL FUNCTION
def p_internal(t: Token) -> None:
    "statement : INTERNAL FUNCTION FUNCALL application EQUALS expression"
    t[0] = ("internal", t[3], tuple(t[4]), t[6])


def p_internal_function0(t: Token) -> None:
    "statement : INTERNAL FUNCTION application"
    t[0] = ("internal_function", tuple(t[3]))


def p_internal_function1(t: Token) -> None:
    "statement : INTERNAL FUNCTION"
    t[0] = ("internal_function", [])


## NORMAL MODE IS
def p_normal(t: Token) -> None:
    "statement : NORMAL MODE IS typeval"
    t[0] = ("normal", t[4])


def p_typeval_float(t: Token) -> None:
    "typeval : FLOATING_POINT"
    t[0] = "double"
    nomalmode = "double"


def p_typeval_int(t: Token) -> None:
    "typeval : INTEGER"
    t[0] = "int64_t"
    nomalmode = "int64_t"


def p_typeval_bool(t: Token) -> None:
    "typeval : BOOLEAN"
    t[0] = "bool"
    nomalmode = "bool"


## OTHERWISE
def p_otherwise(t: Token) -> None:
    "statement : OTHERWISE"
    t[0] = ("else",)


## PAUSE
def p_pause(t: Token) -> None:
    "statement : PAUSE expression"
    t[0] = ("pause", t[2])


## PRINT_COMMENT
def p_print_comment(t: Token) -> None:
    "statement : PRINT_COMMENT STRING"
    t[0] = ("print_comment", t[2])


## PRINT_ONLINE_FORMAT
def p_print_online_format(t: Token) -> None:
    "statement : PRINT_ONLINE_FORMAT NAME COMMA outlist"
    global emitformatter
    emitformatter = True
    t[0] = ("print_online", ("variable", t[2])) + tuple(t[4])


def p_print_online_format0(t: Token) -> None:
    "statement : PRINT_ONLINE_FORMAT STRING COMMA outlist"
    global emitformatter
    emitformatter = True
    t[0] = ("print_online", ("char", t[2])) + tuple(t[4])


def p_print_online_format1(t: Token) -> None:
    "statement : PRINT_ONLINE_FORMAT NAME"
    global emitformatter
    emitformatter = True
    t[0] = ("print_online", ("variable", t[2]))


def p_print_online_format2(t: Token) -> None:
    "statement : PRINT_ONLINE_FORMAT STRING"
    global emitformatter
    emitformatter = True
    t[0] = ("print_online", ("char", t[2]))


## WRITE_BCD_TAPE
def p_write_bcd_tape(t: Token) -> None:
    "statement : WRITE_BCD_TAPE INTNUM COMMA NAME COMMA outlist"
    global emitformatter, emittapeio
    emitformatter = True
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("writebcd", ("intnum", t[2]), ("variable", t[4])) + tuple(t[6])


def p_write_bcd_tape0(t: Token) -> None:
    "statement : WRITE_BCD_TAPE INTNUM COMMA STRING COMMA outlist"
    global emitformatter, emittapeio
    emitformatter = True
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("writebcd", ("intnum", t[2]), ("char", t[4])) + tuple(t[6])


def p_write_bcd_tape1(t: Token) -> None:
    "statement : WRITE_BCD_TAPE INTNUM COMMA NAME"
    global emitformatter, emittapeio
    emitformatter = True
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("writebcd", ("intnum", t[2]), ("variable", t[4]))


def p_write_bcd_tape2(t: Token) -> None:
    "statement : WRITE_BCD_TAPE INTNUM COMMA STRING"
    global emitformatter, emittapeio
    emitformatter = True
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("writebcd", ("intnum", t[2]), ("char", t[4]))


## WRITE_BINARY_TAPE
def p_write_binary_tape(t: Token) -> None:
    "statement : WRITE_BINARY_TAPE INTNUM COMMA outlist"
    global emittapeio
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("writebin", ("intnum", t[2])) + tuple(t[4])


## PRINT_FORMAT
def p_print_format(t: Token) -> None:
    "statement : PRINT_FORMAT NAME COMMA outlist"
    global emitformatter
    emitformatter = True
    t[0] = ("print", ("variable", t[2])) + tuple(t[4])


def p_print_format0(t: Token) -> None:
    "statement : PRINT_FORMAT STRING COMMA outlist"
    global emitformatter
    emitformatter = True
    t[0] = ("print", ("char", t[2])) + tuple(t[4])


def p_print_format1(t: Token) -> None:
    "statement : PRINT_FORMAT NAME"
    global emitformatter
    emitformatter = True
    t[0] = ("print", ("variable", t[2]))


def p_print_format2(t: Token) -> None:
    "statement : PRINT_FORMAT STRING"
    global emitformatter
    emitformatter = True
    t[0] = ("print", ("char", t[2]))
    # This is shared with SAVE DATA


def p_outlist_continue(t: Token) -> None:
    "outlist : outlist COMMA outval"
    t[0] = t[1] + [t[3]]


def p_outlist(t: Token) -> None:
    "outlist : outval"
    t[0] = [t[1]]


def p_outval(t: Token) -> None:
    """outval : range
    | expression"""
    t[0] = t[1]


def p_range(t: Token) -> None:
    "range : NAME application DOTRANGE NAME application"
    t[0] = ("range", t[1], t[2][0], t[5][0])
    if t[1] != t[4]:
        global sourceline
        sourceline = lexer.lineno(1)
        nonfatal("Variable names in range don't match.")


## PRINT_RESULTS
def p_print_results(t: Token) -> None:
    "statement : PRINT_RESULTS varlist"
    t[0] = ("print_results", t[2])


## R
def p_remark(t: Token) -> None:
    "statement : COMMENT STRING"
    t[0] = ("remark", t[2])


def p_remark1(t: Token) -> None:
    "statement : COMMENT"
    t[0] = ("remark",)


## READ_AND_PRINT_DATA
def p_read_and_print_data(t: Token) -> None:
    "statement : READ_AND_PRINT_DATA"
    global emitreadprint
    emitreadprint = True
    headers.append("ctype")
    t[0] = ("read_and_print_data",)


## READ_DATA
def p_read_data(t: Token) -> None:
    "statement : READ_DATA"
    global emitreadprint
    emitreadprint = True
    headers.append("ctype")
    t[0] = ("read_data",)


## READ_BCD_TAPE
def p_read_bcd_tape(t: Token) -> None:
    "statement : READ_BCD_TAPE INTNUM COMMA NAME COMMA varlist"
    global emitscanf, emitformatter, emittapeio
    emitscanf = True
    emitformatter = True
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("readbcd", ("intnum", t[2]), ("variable", t[4])) + tuple(t[6])


def p_read_bcd_tape0(t: Token) -> None:
    "statement : READ_BCD_TAPE INTNUM COMMA STRING COMMA varlist"
    global emitscanf, emitformatter, emittapeio
    emitscanf = True
    emitformatter = True
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("readbcd", ("intnum", t[2]), ("char", t[4])) + tuple(t[6])


## READ_BINARY_TAPE
def p_read_binary_tape(t: Token) -> None:
    "statement : READ_BINARY_TAPE INTNUM COMMA varlist"
    global emittapeio
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("readbin", ("intnum", t[2])) + tuple(t[4])


## READ_FORMAT
def p_read_format(t: Token) -> None:
    "statement : READ_FORMAT NAME COMMA varlist"
    global emitscanf, emitformatter
    emitscanf = True
    emitformatter = True
    t[0] = ("read", ("variable", t[2])) + tuple(t[4])


def p_read_format0(t: Token) -> None:
    "statement : READ_FORMAT STRING COMMA varlist"
    global emitscanf, emitformatter
    emitscanf = True
    emitformatter = True
    t[0] = ("read", ("char", t[2])) + tuple(t[4])


# This is shared with RESTORE_DATA
def p_varlist_continue(t: Token) -> None:
    "varlist : varlist COMMA lval"
    t[0] = t[1] + [t[3]]


def p_varlist(t: Token) -> None:
    "varlist : lval"
    t[0] = [t[1]]


def p_lval(t: Token) -> None:
    """lval : range
    | NAME"""
    t[0] = t[1]


## BACKSPACE_RECORD_OF_TAPE
def p_backspace_record_of_tape(t: Token) -> None:
    "statement : BACKSPACE_RECORD_OF_TAPE INTNUM"
    global emittapeio
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("backspacetape", ("intnum", t[2]))


## END_OF_FILE_TAPE
def p_end_of_file_tape(t: Token) -> None:
    "statement : END_OF_FILE_TAPE INTNUM"
    global emittapeio
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("endfiletape", ("intnum", t[2]))


## REWIND_TAPE
def p_rewind_tape(t: Token) -> None:
    "statement : REWIND_TAPE INTNUM"
    global emittapeio
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("rewindtape", ("intnum", t[2]))


## UNLOAD_TAPE
def p_unload_tape(t: Token) -> None:
    "statement : UNLOAD_TAPE INTNUM"
    global emittapeio
    emittapeio = True
    file = "Tape_" + str(t[2])
    tapeluns[file] = ("intnum", t[2])
    t[0] = ("unloadtape", ("intnum", t[2]))


## RESTORE
def p_restore_data(t: Token) -> None:
    "statement : RESTORE DATA varlist"
    global emitcursor
    emitcursor = True
    t[0] = ("restore", tuple(t[3]))


def p_restore_return(t: Token) -> None:
    "statement : RESTORE_RETURN"
    t[0] = ("restore_return",)


## SAVE
def p_save_data(t: Token) -> None:
    "statement : SAVE DATA outlist"
    global emitcursor
    emitcursor = True
    t[0] = ("save", tuple(t[3]))


def p_save_return(t: Token) -> None:
    "statement : SAVE_RETURN"
    t[0] = ("save_return",)


## SET_LIST_TO
def p_set_list(t: Token) -> None:
    "statement : SET_LIST_TO NAME"
    global emitchkcursor
    emitchkcursor = True
    set_lists.append((t[2], ("intnum", "-1")))
    if t[2] in typedict:
        del typedict[t[2]]
    t[0] = ("set_list_to", t[2], ("intnum", "-1"))


def p_set_list0(t: Token) -> None:
    "statement : SET_LIST_TO NAME COMMA expression"
    global emitchkcursor
    emitchkcursor = True
    set_lists.append((t[2], t[4]))
    if t[2] in typedict:
        del typedict[t[2]]
    t[0] = ("set_list_to", t[2], t[4])


## THROUGH
def p_through_for(t: Token) -> None:
    "statement : THROUGH NAME COMMA FOR NAME EQUALS expression COMMA expression COMMA expression"
    if t[5] not in typedict:
        typedict[t[5]] = normalmode
    t[0] = ("for", t[2], t[5], t[7], t[9], t[11])


def p_through_map(t: Token) -> None:
    "statement : THROUGH NAME COMMA FOR VALUES OF NAME EQUALS value_list"
    t[0] = ("map", t[2], t[7], t[9])


def p_map(t: Token) -> None:
    "value_list : expression"
    t[0] = [t[1]]


def p_map_continue(t: Token) -> None:
    "value_list : value_list COMMA expression"
    t[0] = t[1] + [t[3]]


## TRANSFER TO
def p_transfer(t: Token) -> None:
    "statement : TRANSFER TO NAME"
    t[0] = ("goto", t[3])


def p_transfer0(t: Token) -> None:
    "statement : TRANSFER TO NAME LPAREN expression RPAREN"
    t[0] = ("switch", t[3], t[5])


## WHENEVER
def p_whenever_simple(t: Token) -> None:
    "statement : WHENEVER expression COMMA statement"
    t[0] = ("if", t[2], t[4])


def p_whenever_complex(t: Token) -> None:
    "statement : WHENEVER expression"
    t[0] = ("whenever", t[2])


def p_whenever_or(t: Token) -> None:
    "statement : OR WHENEVER expression"
    t[0] = ("or", t[3])


# VECTOR VALUES
def p_vector_values_string(t: Token) -> None:
    "statement : VECTOR VALUES NAME EQUALS STRING"
    typedict[t[3]] = "char"
    t[0] = ("values",) + (t[3], [("char", '"' + t[5] + '"')])


def p_vector_values_array(t: Token) -> None:
    "statement : VECTOR VALUES NAME EQUALS arglist"
    t[0] = ("values",) + (t[3], t[5])


def p_vector_values_array0(t: Token) -> None:
    "statement : VECTOR VALUES NAME LPAREN INTNUM RPAREN EQUALS arglist"
    t[0] = ("values",) + (t[3], t[8])


## Assignment
def p_statement_substitution1(t: Token) -> None:
    "statement : NAME EQUALS expression"
    t[0] = ("substitution", ("variable", t[1]), t[3])


def p_statement_substitution2(t: Token) -> None:
    "statement : arrayref EQUALS expression"
    t[0] = ("substitution", tuple(["variable"] + t[1][0]), t[3])


def p_arrayref(t: Token) -> None:
    "arrayref : NAME application"
    t[0] = [[t[1], tuple(t[2])]]


## STATEMENT LABEL
def p_statement_label(t: Token) -> None:
    "statement : STATEMENT_LABEL labellist"
    t[0] = ("statement_label", t[2])


def p_labellist(t: Token) -> None:
    "labellist : NAME"
    stmtlabels[t[1]] = stmtlabels.get(t[1], 0) + 1
    t[0] = [t[1]]


def p_labellist_continue(t: Token) -> None:
    "labellist : labellist COMMA NAME"
    stmtlabels[t[3]] = stmtlabels.get(t[3], 0) + 1
    t[0] = t[1] + [t[3]]


## INSERT FILE
def p_insert_file(t: Token) -> None:
    "statement : INSERT_FILE NAME"
    t[0] = ("insert_file", t[2])


# Expression parsing


def p_expression_binop1(t: Token) -> None:
    """expression : expression PLUS expression
    | expression MINUS expression
    | expression TIMES expression
    | expression DIVIDE expression"""
    t[0] = (t[2].lower(), t[1], t[3])


def p_expression_binop2(t: Token) -> None:
    """expression : expression OR expression
    | expression AND expression
    | expression THEN expression
    | expression EQV expression
    | expression LT expression
    | expression LE expression
    | expression GT expression
    | expression GE expression
    | expression EQ expression
    | expression NE expression
    | expression POW expression
    | expression LS expression
    | expression RS expression
    | expression A expression
    | expression V expression
    | expression EV expression
    | expression MOD expression
    """
    global headers
    op = t[2][1:-1].lower()
    t[0] = (op, t[1], t[3])
    if op == "p" and "math" not in headers:
        headers.append("math")


def p_expression_not(t: Token) -> None:
    "expression : NOT expression %prec NOT"
    t[0] = ("not", t[2])


def p_expression_uminus(t: Token) -> None:
    "expression : MINUS expression %prec UMINUS"
    t[0] = ("-", t[2])


def p_expression_uplus(t: Token) -> None:
    "expression : PLUS expression %prec UPLUS"
    t[0] = ("+", t[2])


def p_expression_abs(t: Token) -> None:
    "expression : ABS expression %prec ABS"
    if "stdlib" not in headers:
        headers.append("stdlib")
    if "math" not in headers:
        headers.append("math")
    t[0] = ("abs", t[2])


def p_expression_group(t: Token) -> None:
    "expression : LPAREN expression RPAREN"
    t[0] = t[2]


def p_expression_bool(t: Token) -> None:
    "expression : BOOLNUM"
    t[0] = ("boolnum", t[1])


def p_expression_octint(t: Token) -> None:
    "expression : OCTNUM"
    t[0] = ("intnum", t[1])


def p_expression_integer(t: Token) -> None:
    "expression : INTNUM"
    t[0] = ("intnum", t[1])


def p_expression_string(t: Token) -> None:
    "expression : STRING"
    global emitpackstr
    emitpackstr = True
    t[0] = ("char", '"' + t[1] + '"')


def p_expression_float(t: Token) -> None:
    "expression : FLOATNUM"
    t[0] = ("floatnum", t[1])


def p_expression_subscript(t: Token) -> None:
    "expression : NAME application"
    t[0] = ("variable", t[1], tuple(t[2]))
    var = t[1]
    refcount[var] = refcount.get(var, 0) + 1
    arraydepths[var] = len(t[2])


def p_application(t: Token) -> None:
    "application : LPAREN arglist RPAREN"
    t[0] = t[2]


def p_application_tail_empty(t: Token) -> None:
    "arglist : expression"
    t[0] = [t[1]]


def p_application_tail_nonempty(t: Token) -> None:
    "arglist : arglist COMMA expression"
    t[0] = t[1] + [t[3]]


def p_function_application(t: Token) -> None:
    "expression : FUNCALL application"
    t[0] = ("apply", t[1], tuple(t[2]))
    if t[1] not in madmath:
        found = False
        for i in range(len(executefunctions)):
            (currentfunc, args) = executefunctions[i]
            if currentfunc == t[1]:
                found = True
                break
        if not found:
            executefunctions.append((t[1], t[2]))
    global headers
    if t[1] in madmath and "math" not in headers:
        headers.append("math")


def p_expression_funcall0(t: Token) -> None:
    "expression : FUNCALL"
    var = t[1]
    t[0] = ("funptr", var[:-1])
    refcount[var] = refcount.get(var, 0) + 1


def p_expression_name(t: Token) -> None:
    "expression : NAME"
    var = t[1]
    t[0] = ("variable", var)
    if var not in typedict:
        typedict[var] = normalmode
    refcount[var] = refcount.get(var, 0) + 1


# Error handling


def p_error(t: Optional[Token]) -> None:
    "Gets fed an error token."
    global sourceline
    sourceline = lexer.lineno
    if t is None:
        nonfatal("Syntax error at EOF")
        return
    nonfatal('Syntax error on token "%s"' % t.value)


yacc.yacc(debug=0)

# These will require the math library.
# All but tan. are in the manual examples.
madmath = ("elog.", "sqrt.", "sin.", "cos.", "tan.", "exp.")
# Map MAD intrinsics to C library functions
madfunc_map = {"elog": "log", "rename": "Crename", "exit": "Cexit"}

tapefuncs = "seteof."
ctssfuncs = (
    "assign.",
    "bin.",
    "chfile.",
    "chncom.",
    "comarg.",
    "delfil.",
    "rename.",
    "seek.",
    "whoami.",
)

verbose = 0
errcount = 0


def nonfatal(msg: str) -> None:
    global errcount, sourcefile, sourceline
    sys.stderr.write("%s:%d: %s\n" % (sourcefile, sourceline, msg))
    errcount += 1


def fatal(msg: str) -> None:
    nonfatal(msg)
    sys.exit(1)


# All MAD variables are global, you have to simulate locals using SAVE DATA and
# RETURN DATA.  Thus the variable lookup tables about are straight
# dictionaries not qualified by current function name.


def analyze() -> None:
    "Gather variable types and determine function boundaries."
    global deck, hasmain, funcargs, funcname
    global sourcefile, sourceline
    if not deck:
        return
    # We may need to generate a pseudo-function call
    for i in range(len(deck)):
        if deck[i][3][0] != "remark":
            break
    if deck[i][3][0] != "external_function" and deck[i][3][0] != "internal_function":
        deck.insert(i, (deck[i][0], i, None, ("entry", "main.", ())))
        hasmain = True

    # Sanity check -- all labels in through statements must hit targets
    labels = [x for x in [x[2] for x in deck] if x]
    for sourcefile, sourceline, label, statement in deck:
        if label != None:
            stmtlabels[label] = stmtlabels.get(label, 0) + 1
            if label in typedict:
                typedict[label] = "int64_t"
        if statement[0] == "for":
            target = statement[1]
            forlabels[target] = forlabels.get(target, 0) + 1
            var = statement[2]
            # print "for1: var = %s, type = %s" % (var, typedict[var])
            if target not in labels:
                nonfatal("FOR label '%s' not defined." % target.upper())

    # On to actual analysis
    try:
        # Mine the code for compile-time directives
        for sourcefile, sourceline, label, statement in deck:
            if statement[0] == "boolean":
                for var in statement[1:]:
                    typedict[var] = "bool"
            elif statement[0] == "common":
                for metadata in statement[1]:
                    common[metadata[0]] = metadata[1:]
                    if int(metadata[1][0]) > 0:
                        dimensions[metadata[0]] = metadata[1:]
            elif statement[0] == "dimension":
                for metadata in statement[1]:
                    dimensions[metadata[0]] = metadata[1:]
            elif statement[0] == "set_list_to":
                var = statement[1]
                if var not in dimensions:
                    fatal(
                        "SET LIST TO requires a dimensioned array, got '%s'."
                        % var.upper()
                    )
            elif statement[0] == "integer":
                for var in statement[1:]:
                    typedict[var] = "int64_t"
            elif statement[0] == "floating_point":
                for var in statement[1:]:
                    typedict[var] = "double"
            elif statement[0] == "normal":
                global normalmode
                normalmode = statement[1]
                for var in typedict:
                    if typedict[var] == "default":
                        typedict[var] = normalmode
            elif statement[0] in (
                "print",
                "read",
                "print_comment",
                "print_results",
                "read_and_print_data",
                "read_data",
                "print_online",
                "abs",
                "pause",
                "writebcd",
                "writebin",
                "readbcd",
                "readbin",
                "error_return",
            ):
                if "stdio" not in headers:
                    headers.append("stdio")
                if "stdlib" not in headers:
                    headers.append("stdlib")
                if "string" not in headers:
                    headers.append("string")
                if "ctype" not in headers:
                    headers.append("ctype")
                if "errno" not in headers:
                    headers.append("errno")
            elif statement[0] == "values":
                var = statement[1]
                vtype = statement[2][0][0]
                if vtype == "variable":
                    stmtlabels[var] = stmtlabels.get(var, 0) + 1
                    switchtables[var] = statement[2]
                    for vtype, label in statement[2]:
                        # print "vtype = %s, label = %s" % (vtype, label)
                        targets[label] = targets.get(label, 0) + 1
                else:
                    if len(statement[2]) > 1:
                        dimensions[var] = (len(statement[2]),)
                    vecvalues[var] = statement[2]
                    if vtype == "boolnum":
                        typedict[var] = "bool"

        # First associate entry point names with function definitions
        # We do this first before we reorder the code.
        functype = ""
        funcname = ""
        funcargs = {}
        for i in range(len(deck)):
            (sourcefile, sourceline, label, statement) = deck[i]
            # print "Deck[%d]" % i
            # print "   orig:", deck[i]
            if statement[0] == "external_function":
                functype = statement[0]
                funcargs = statement[1]
            elif statement[0] == "internal_function":
                functype = statement[0]
                funcargs = statement[1]
            elif statement[0] == "entry" and len(statement) == 2:
                funcname = statement[1]
                if functype == "external_function":
                    for j in range(len(executefunctions)):
                        (execfunc, execargs) = executefunctions[j]
                        if funcname == execfunc:
                            executefunctions.pop(j)
                            break
                    deck[i] = (
                        sourcefile,
                        sourceline,
                        label,
                        ("entry", funcname, funcargs),
                    )
                    # print "    new:", deck[i]
                elif functype == "internal_function":
                    deck[i] = (
                        sourcefile,
                        sourceline,
                        label,
                        ("entry", funcname, funcargs),
                    )
                    # print "   new", deck[i]
                else:
                    nonfatal("No FUNCTION declaration matches ENTRY %s" % funcname)

        # Reorder code tuples so internal functions are just after entry points
        lastentry = -1
        savelist = []
        internalcount = 0
        for i in range(len(deck)):
            (sourcefile, sourceline, label, statement) = deck[i]
            # print "Deck-1[%d] statment:" % i
            # print "           ", statement
            if statement[0] == "entry":
                lastentry = i
                internalcount = 1
                for line in savelist:
                    deck.insert(i, deck.pop(line))
                savelist = []
            elif statement[0] == "internal":
                for j in range(len(executefunctions)):
                    (execfunc, execargs) = executefunctions[j]
                    if statement[1] == execfunc:
                        executefunctions.pop(j)
                        break
                if lastentry != -1:
                    deck.insert(lastentry + internalcount, deck.pop(i))
                else:
                    savelist.append(i)

        # Reorder code tuples so SET LIST arrays are global.
        firstentry = -1
        for i in range(len(deck)):
            (sourcefile, sourceline, label, statement) = deck[i]
            if statement[0] == "entry" and firstentry == -1:
                firstentry = i
            elif firstentry != -1 and statement[0] == "set_list_to":
                deck.insert(firstentry, deck.pop(i))

        # Do type deductions from format fields
        for card in deck:
            (sourcefile, sourceline, label, statement) = card
            if statement[0] in ("read", "print", "print_online", "writebcd", "readbcd"):
                if statement[0] in ("writebcd", "readbcd"):
                    (fmttype, fmtval) = statement[2]
                else:
                    (fmttype, fmtval) = statement[1]
                if fmttype == "variable":
                    if fmtval in vecvalues:
                        fmt = vecvalues[fmtval]
                        format[fmtval] = statement[0]
                        if statement[0] in ("read", "readbcd"):
                            (types, fmt) = fmt[0]
                            types = deduce_types(fmt_convert(fmt))
                            for vtype, var in zip(types, statement[2:]):
                                if type(var) == type(()) and var[0] == "range":
                                    typedict[var[1]] = vtype
                                else:
                                    typedict[var] = vtype
                    else:
                        typedict[fmtval] = "char"

        # Put internal functions before first entry.
        firstentry = -1
        for i in range(len(deck)):
            (sourcefile, sourceline, label, statement) = deck[i]
            if statement[0] == "entry" and firstentry == -1:
                firstentry = i
            elif statement[0] == "internal_function":
                # assume that prior remarks go with this function.
                args = statement[1]
                while deck[i - 1][3][0] == "remark":
                    (sourcefile, sourceline, label, statement) = deck[i - 1]
                    i = i - 1
                while i in range(len(deck)) and statement[0] != "end_function":
                    (sourcefile, sourceline, label, statement) = deck[i]
                    if statement[0] == "entry":
                        internalfunctions.append((statement[1], args))
                    deck.insert(firstentry, deck.pop(i))
                    firstentry = firstentry + 1
                    i = i + 1

        # for i in range(len(deck)):
        #    (sourcefile, sourceline, label, statement) = deck[i]
        #    print "Deck-2[%d] statment = " % i
        #    print "    ", statement

        # Type inference loop.  We initialized for this by deducing types
        # from READ FORMAT statements. INTEGER, and BOOLEAN declarations
        # above.  Now propagate markers of non-normalmode type through
        # expression evaluation until we can't deduce any more int or bool
        # type bindings for variables.  Everything else just defaults to
        # float.
        currentfunc = None
        currmode = normalmode
        if currmode == "default":
            currmode = "double"
        while True:
            deductions = 0
            for card in deck:
                (sourcefile, sourceline, label, statement) = card
                if statement[0] == "entry":
                    # Bookkeeping, so we know what function we're in
                    currentfunc = statement[1]
                    # print "entry: ", statement
                elif statement[0] == "return" and len(statement) > 1:
                    # Give the function the type of the last return statement.
                    # We're screwed if two different returns in a function have
                    # different modes.
                    m = mode(statement[1])
                    returntypes[currentfunc] = m
                    if m != currmode and returntypes[currentfunc] != m:
                        deductions += 1
                elif statement[0] == "restore":
                    for var in statement[1]:
                        if var not in typedict:
                            typedict[var] = currmode
                            deductions += 1
                elif statement[0] == "substitution":
                    # Propagate type markers through assignment
                    # print "substitute: ", statement
                    var = statement[1][1]
                    m = mode(statement[2])
                    prev = typedict.get(var)
                    typedict[var] = m
                    if m != currmode and prev != m:
                        deductions += 1
            if deductions == 0:
                break

        # We also check 'apply' statements for statement label arguments
        functype = ""
        funcname = ""
        funcargs = {}
        skip = -1
        decklen = len(deck)
        i = 0
        while i < decklen:
            # print "skip = %d, i = %d" % (skip, i)
            if i == skip:
                skip = -1
                continue
            (sourcefile, sourceline, label, statement) = deck[i]
            # print "Deck-3[%d]:" % i
            # print "   ", deck[i]
            if statement[0] == "external_function":
                functype = statement[0]
                funcargs = statement[1]
            elif statement[0] == "internal_function":
                functype = statement[0]
                funcargs = statement[1]
            elif statement[0] == "entry":
                # print "entry: ", statement
                funcname = statement[1]
                funcargs = statement[2]
            else:
                skip = analyze_statement(i, statement)
                if skip != 0:
                    # print "skip = %d, i = %d" % (skip, i)
                    decklen = len(deck)
                    i += 1
            i += 1
        # for i in range(len(deck)):
        #    print "Deck-4[%d]:" % i
        #    print "    ", deck[i]

    except:
        nonfatal("Internal error while analyzing code")
        (exc_type, exc_value, exc_traceback) = sys.exc_info()
        raise exc_type(exc_value).with_traceback(exc_traceback)


def analyze_statement(index: int, statement: Any) -> int:
    global funcname
    if statement[0] == "execute":
        return analyzeC1(index, statement[1])
    elif statement[0] == "external_call":
        return analyzeC1(index, ("apply", statement[1], statement[2]))
    elif statement[0] == "if":
        return analyzeC1(index, statement[2])
    elif statement[0] in ("or", "whenever"):
        return analyzeC1(index, statement[1])
    elif statement[0] == "return":
        if len(statement) == 1:
            return 0
        else:
            m = mode(statement[1])
            returntypes[funcname] = m
            return analyzeC1(index, statement[1])
    elif statement[0] == "substitution":
        var = statement[1][1]
        assigntargets[var] = assigntargets.get(var, 0) + 1
        return analyzeC1(index, statement[2])
    elif statement[0] == "goto":
        # print "goto-1: stmt: ", statement
        if statement[1] not in stmtlabels:
            nonfatal("TRANSFER label '%s' not defined." % statement[1].upper())
        targets[statement[1]] = targets.get(statement[1], 0) + 1
        return 0
    else:
        return 0


def analyzeC1(index: int, t: Any) -> int:
    global deck, funcargs, funcname
    if t[0] == "apply":
        # print "apply stmt: ", t, index
        # print "funcargs: ", funcargs
        # print "funcname: ", funcname
        # print "stmtlabels: ", stmtlabels
        i = index
        for j in range(len(t[2])):
            # print "arg: ", t[2][j], len(t[2][j])
            if len(t[2][j]) == 2:
                (argtype, argvar) = t[2][j]
                if t[1] == "seteof.":
                    targets[argvar] = targets.get(argvar, 0) + 1
                    return 0
                if argvar in stmtlabels:
                    (sourcefile, sourceline, label, statement) = deck[index]
                    if label in forlabels:
                        deck[index] = (sourcefile, sourceline, None, statement)
                    varfound = False
                    for vtype, var in funcargs:
                        if argvar == var:
                            varfound = True
                            break
                    deck.insert(
                        i,
                        (
                            sourcefile,
                            sourceline,
                            None,
                            ("substitution", ("variable", argvar), ("intnum", "0")),
                        ),
                    )
                    deck.insert(
                        i + 2,
                        (
                            sourcefile,
                            sourceline,
                            None,
                            ("whenever", ("ne", ("variable", argvar), ("intnum", "0"))),
                        ),
                    )
                    # print "varfound = ", varfound
                    if varfound:
                        if funcname in returntypes:
                            if returntypes[funcname] == "int64_t":
                                retval = ("intnum", "0")
                            elif returntypes[funcname] == "bool":
                                retval = ("boolnum", "0")
                            else:
                                retval = ("floatnum", "0")
                            deck.insert(
                                i + 3,
                                (sourcefile, sourceline, None, ("return", retval)),
                            )
                        else:
                            deck.insert(
                                i + 3, (sourcefile, sourceline, None, ("return",))
                            )
                    else:
                        targets[argvar] = targets.get(argvar, 0) + 1
                        deck.insert(
                            i + 3, (sourcefile, sourceline, None, ("goto", argvar))
                        )
                    deck.insert(
                        i + 4, (sourcefile, sourceline, None, ("end_conditional",))
                    )
                    if label in forlabels:
                        deck.insert(
                            i + 5, (sourcefile, sourceline, label, ("continue",))
                        )
                    return index + 1
        return 0
    elif t[0] == "+":
        if len(t) == 2:
            return analyzeC1(index, t[1])
        else:
            index = analyzeC1(index, t[1])
            return analyzeC1(index, t[2])
    elif t[0] == "-":
        if len(t) == 2:
            return analyzeC1(index, t[1])
        else:
            index = analyzeC1(index, t[1])
            return analyzeC1(index, t[2])
    elif t[0] in ("abs", "not"):
        return analyzeC1(index, t[1])
    elif t[0] in ("*", "mod", "/", "l", "g", "le", "ge", "e", "ne", "p", "and"):
        index = analyzeC1(index, t[1])
        return analyzeC1(index, t[2])
    elif t[0] in ("or", "then", "eqv", "ls", "v", "ev", "a", "rs"):
        index = analyzeC1(index, t[1])
        return analyzeC1(index, t[2])
    elif t[0] == "return" and len(t) > 1:
        m = mode(t[1])
        returntypes[funcname] = m
        return 0
    elif t[0] == "goto":
        # print "goto-2: stmt: ", t
        if t[1] not in stmtlabels:
            nonfatal("TRANSFER label '%s' not defined." % t[1].upper())
        targets[t[1]] = targets.get(t[1], 0) + 1
        return 0
    elif t[0] == "switch":
        if t[1] not in switchtables:
            nonfatal("TRANSFER label '%s' not defined." % t[1].upper())
        return 0
    else:
        return 0


def mode(t: Any) -> str:
    "Compute the return type of an expression"
    # print "->mode", t
    if t[0] == "variable":
        # print "<-mode of ", t[1], typedict.get(t[1], normalmode)
        currtype = typedict.get(t[1], normalmode)
        if currtype == "default":
            currtype = "double"
        global ctssmode
        if currtype == "char" and ctssmode:
            currtype = "int64_t"
        return currtype
    elif t[0] in ("p", "abs") or t[0] in ("+", "-") and len(t) == 2:
        # print "<-mode of ", t[1]
        return mode(t[1])
    elif t[0] == "intnum":
        # print "<-mode of integer constant", t[1]
        return "int64_t"
    elif t[0] == "floatnum" or t[0] == "default":
        # print "<-mode of float constant", t[1]
        return "double"
    elif t[0] == "boolnum":
        # print "<-mode of boolean constant", t[1]
        return "bool"
    elif t[0] == "char":
        # Yes, MAD considers strings to have int type, see section
        # 2.10.  In the original 709 implementation they were 6-byte
        # constants that fit in a 36-bit machine word.
        # print "<-mode of string ", t[1]
        return "int64_t"
    elif t[0] == "apply":
        if normalmode == "default":
            return returntypes.get(t[1], "double")
        return returntypes.get(t[1], normalmode)
    elif t[0] in logops:
        # print "<-bool (logical operator)", t[1]
        return "bool"
    else:
        left = mode(t[1])
        right = mode(t[2])
        # print "left mode %s, right mode %s" % (left, right)
        if left == "default" or right == "default":
            # print "<-mode of binary operand"
            return "double"
        if left == "double" or right == "double":
            # print "<-mode of binary operand"
            return "double"
        if left == "int64_t" or right == "int64_t":
            # print "<-mode of binary operand"
            return "int64_t"
        nonfatal("Internal error while computing mode of %s." % repr(t))
        sys.exit(1)


def prinC1(t: Any) -> str:
    global ctssmode, emitargs
    global packstrings, tapeeofvector
    "Recursive print of expression value"
    if t[0] == "variable":
        varfound = False
        global funcargs
        # print "funcargs: ", funcargs, t
        for vtype, var in funcargs:
            if t[1] == var:
                varfound = True
                break
        if t[1] in stmtlabels:
            if varfound:
                if emitargs:
                    res = t[1]
                else:
                    res = "*" + t[1]
            else:
                if emitargs:
                    res = "&" + t[1]
                else:
                    res = t[1]
            return res
        res = t[1]
        if len(t) > 2:
            subscript = print_C_expr(t[2][0])
            if len(t[2]) > 1:
                # More than one subscript; this is where we implement MAD's
                # weird reliance on dope vectors.
                # try:
                #    ptr = dimensions[t[1]][1]
                # except KeyError:
                #    fatal("attempt to reference undimensioned array %s" % t[1])
                # if len(dimensions[t[1]]) > 2:
                #    ptr = ptr + "+" + dimensions[t[1]][2]
                # subscript = 'MAD' + `len(t[2])` + '(' + ptr + "," + ','.join(map(print_C_expr, t[2])) + ')'
                # Convert to a single dimension index.
                for expr in t[2][1:]:
                    # print "exp: ",expr
                    subscript += "*" + print_C_expr(expr)
            if ctssmode and t[1] in vecvalues:
                if vecvalues[t[1]][0][0] == "char":
                    res = "PackStr(" + res + "[" + subscript + "])"
                else:
                    res += "[" + subscript + "]"
            elif emitargs:
                res = "&" + res + "[" + subscript + "]"
            else:
                res += "[" + subscript + "]"
        else:
            if res in parameters:
                res = res.upper()
            elif res in dimensions:
                if emitargs:
                    if arraydepths.get(t[1], 0) > 0:
                        res = "&" + res + "[0]"
                else:
                    res += "[0]"
            elif ctssmode and t[1] in vecvalues:
                if vecvalues[t[1]][0][0] == "char":
                    res = "PackStr(" + t[1] + ")"
            elif varfound and t[1] in assigntargets:
                res = "*" + t[1]
            else:
                for i in range(len(set_lists)):
                    (var, args) = set_lists[i]
                    if res == var:
                        res += "[0]"
                        break
        return res
    elif t[0] == "funptr":
        return t[1]
    elif t[0] == "apply":
        madfunc = t[1][:-1]
        if madfunc == "seteof":
            (vtype, var) = t[2][0]
            tapeeofvector = var
            return "seteof(&" + tapeeofvector + ")"
        if not ctssmode:
            packstrings = False
        emitargs = True
        et = (
            madfunc_map.get(madfunc, madfunc) + "(" + ", ".join(map(prinC1, t[2])) + ")"
        )
        packstrings = True
        emitargs = False
        return et
    elif t[0] == "+":
        if len(t) == 2:
            return "%s" % prinC1(t[1])
        else:
            return "(%s + %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "-":
        if len(t) == 2:
            return "-%s" % prinC1(t[1])
        else:
            return "(%s - %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "not":
        return "!%s" % prinC1(t[1])
    elif t[0] == "*":
        return "(%s * %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "mod":
        return "(%s %% %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "/":
        return "(%s / %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "l":
        return "(%s < %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "g":
        return "(%s > %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "le":
        return "(%s <= %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "ge":
        return "(%s >= %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "e":
        return "(%s == %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "ne":
        return "(%s != %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "p":
        return "pow(%s, %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "and":
        return "(bool)(%s && %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "or":
        return "(bool)(%s || %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "then":
        return "!(%s && !(%s))" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "eqv":
        return "(%s == %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "ls":
        return "((%s << %s) & 0xFFFFFFFFFFFF)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "v":
        return "(%s | %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "ev":
        return "(%s ^ %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "a":
        return "(%s & %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "rs":
        return "(%s >> %s)" % (prinC1(t[1]), prinC1(t[2]))
    elif t[0] == "abs":
        argmode = mode(t[1])
        if argmode == "double":
            return "fabs(%s)" % prinC1(t[1])
        elif argmode == "int64_t":
            return "llabs(%s)" % prinC1(t[1])
        else:
            return "abs(%s)" % prinC1(t[1])  # bool
    elif t[0] == "char":
        if packstrings or ctssmode:
            if len(t[1]) > 8:  # account for quotes in value
                nonfatal("String too long for expression.")
            return "PackStr(" + t[1] + ")"
        else:
            return t[1]
    elif t[0] == "intnum":
        return t[1] + "L"
    elif t[0] == "floatnum":
        return t[1]
    elif t[0] == "boolnum":
        return str(t[1])
    elif type(t) == type(()) and t[0] == "range":
        if t[2] == ("intnum", "0"):
            return print_C_expr(("variable", t[1]))
        else:
            return print_C_expr(("+", ("variable", t[1]), t[2]))
    else:
        return "***" + repr(t)


def print_C_expr(t: Any) -> str:
    "Cosmetic wrapper around prinC1"
    res = prinC1(t)
    # if res[0] == "(" and res[-1] == ")":
    #    res = res[1:-1]
    return res


def fmt_convert(fstr: str) -> str:
    "Convert MAD format string to C printf/scanf"
    # Since we do processing at runtime this function just does sanity checks
    # with minimal conversions for the type decoder.
    # Note: If the format variable controls the length of a Hollerith field we
    # could get unpredictable results
    # See the manual on differences between classic MAD and this
    # implementation for discussion.
    if verbose:
        print("->fmt_convert(%s)" % repr(fstr))

    # Check format for format varaibles
    fmt = fstr
    while True:
        if not fmt:
            break
        if fmt[0] == "*":
            break
        elif fmt[0] == "'":
            fmt = fmt[1:]
            i = fmt.find("'")
            if i > 0:
                var = fmt[0:i]
                fmt = fmt[i + 1 :]
                if var.lower() not in formatvars:
                    nonfatal("Undeclared FORMAT VARIABLE '%s'." % var.upper())
            else:
                nonfatal("Unterminated FORMAT VARIABLE '%s'." % fmt.upper())
        else:
            fmt = fmt[1:]

    fmt = fstr
    res = ""
    while True:
        if not fstr:
            nonfatal("Unterminated FORMAT:" + fmt)
            break
        if fstr[0] == "*":
            break
        elif fstr[0] == "/":
            res += "\\n"
            fstr = fstr[1:]
            continue
        elif fstr[0] in ('"', " ", "\n", ","):
            fstr = fstr[1:]
            continue
        elif fstr[0] == "S":
            i = 1
            j = len(fstr)
            while fstr[i] != ",":
                if fstr[i] == "*":
                    break
                i += 1
                if i >= j:
                    break
            if i <= 0:
                nonfatal("Invalid spacer in %s" % fstr)
            field = fstr[1:i]
            res += " "
            fstr = fstr[i:]
            continue
        # All other format codes may take a prefix.
        count = 1
        i = 0
        while fstr[i].isdigit():
            i += 1
        if i > 0:
            count = int(fstr[:i])
            fstr = fstr[i:]
        elif fstr[i] == "'":
            fstr = fstr[1:]
            i = fstr.find("'")
            fstr = fstr[i + 1 :]
            count = 0
        # Now process the actual format item
        if fstr[0] == "(":
            i = fstr.find(")")
            res += fmt_convert(fstr[1:i] + "*") * count
            fstr = fstr[i + 1 :]
        elif fstr[0] == "K":
            i = 1
            while fstr[i] in "0123456789":
                i += 1
            if fstr[i] == "'":
                fstr = fstr[1:]
                i = fstr.find("'")
                fstr = fstr[i + 1 :]
            res += (" %o" * count)[1:]
            fstr = fstr[i:]
        elif fstr[0] == "C":
            i = 1
            while fstr[i] in "0123456789":
                i += 1
            if fstr[i] == "'":
                fstr = fstr[1:]
                i = fstr.find("'")
                fstr = fstr[i + 1 :]
            res += " %s"
            fstr = fstr[i:]
        elif fstr[0] == "E":
            i = 1
            while fstr[i] in "0123456789.":
                i += 1
            if fstr[i] == "'":
                fstr = fstr[1:]
                i = fstr.find("'")
                fstr = fstr[i + 1 :]
            res += (" %e" * count)[1:]
            fstr = fstr[i:]
        elif fstr[0] == "F":
            i = 1
            fmtdot = ".0"
            while fstr[i] in "0123456789.":
                if fstr[i] == ".":
                    fmtdot = ""
                i += 1
            if fstr[i] == "'":
                fstr = fstr[1:]
                i = fstr.find("'")
                fstr = fstr[i + 1 :]
            res += (" %f" * count)[1:]
            fstr = fstr[i:]
        elif fstr[0] == "H":
            if count > 0:
                res += fstr[1 : count + 1]
                fstr = fstr[count + 1 :]
            else:
                i = 1
                j = len(fstr)
                while fstr[i] != "," and fstr[i] != "*":
                    i = i + 1
                    if i >= j:
                        break
                fstr = fstr[i:]
        elif fstr[0] == "I":
            i = 1
            while fstr[i] in "0123456789":
                i += 1
            if fstr[i] == "'":
                fstr = fstr[1:]
                i = fstr.find("'")
                fstr = fstr[i + 1 :]
            res += (" %d" * count)[1:]
            fstr = fstr[i:]
        else:
            nonfatal(
                "Invalid field code %s at %d in format fstring %s!"
                % (repr(fstr[0]), len(fmt) - len(fstr), repr(fmt))
            )
            break
    if verbose:
        print("<-fmt_convert() = %s" % repr(res))
    return res


def deduce_expr(t: Any) -> str:
    mode = normalmode
    if mode == "default":
        mode = "double"
    if t[0] == "variable":
        # print "variable: ", t[1], typedict[t[1]]
        if t[1] in typedict:
            mode = typedict[t[1]]
    elif t[0] == "+":
        # print "'+': ", t
        if len(t) == 2:
            mode = deduce_expr(t[1])
        else:
            mode1 = deduce_expr(t[1])
            mode2 = deduce_expr(t[2])
            mode = mode1
    elif t[0] == "-":
        # print "'-': ", t
        if len(t) == 2:
            mode = deduce_expr(t[1])
        else:
            mode1 = deduce_expr(t[1])
            mode2 = deduce_expr(t[2])
            mode = mode1
    elif t[0] in ("abs", "not"):
        mode = deduce_expr(t[1])
    elif t[0] in ("*", "mod", "/", "l", "g", "le", "ge", "e", "ne", "p", "and"):
        mode1 = deduce_expr(t[1])
        mode2 = deduce_expr(t[2])
        mode = mode1
    elif t[0] in ("or", "then", "eqv", "ls", "v", "ev", "a", "rs"):
        mode1 = deduce_expr(t[1])
        mode2 = deduce_expr(t[2])
        mode = mode1
    elif t[0] == "intnum":
        # print "intnum: ", t[1]
        mode = "int64_t"
    elif t[0] == "boolnum":
        # print "boolnum: ", t[1]
        mode = "int64_t"
    elif t[0] == "floatnum":
        # print "floatnum: ", t[1]
        mode = "double"
    return mode


def deduce_types(fstr: str) -> List[str]:
    "Deduce types from C format string converted from MAD format."
    types = []
    state = 0
    # Simplified by the fact that MAD strings can't contain %.
    for i in range(len(fstr)):
        if fstr[i] == "%":
            state = 1
        elif state == 1:
            if fstr[i].isdigit() or fstr[i] in (".", "-", "+"):
                continue
            if fstr[i] == "s":
                types.append("char")
            elif fstr[i] == "f" or fstr[i] == "e":
                types.append("double")
            elif fstr[i] == "d" or fstr[i] == "o":
                types.append("int64_t")
            else:
                state = 0
    return types


def lpt_codes(st: str) -> str:
    "Interpret line-printer format codes."
    fmtchar = st[0]
    st = st[1:]
    if fmtchar == " ":
        return st
    elif fmtchar == "+":
        return "\r" + st
    elif fmtchar == "0":
        return "\n" + st
    elif fmtchar == "1":
        return "\f" + st
    elif fmtchar == "2":
        return "\v\v" + st
    else:
        return st


def print_C_statement(statement: Any) -> Optional[str]:
    global termbraceemit, hasmain, ctssmode, funcargs, emitargs
    global funcname
    if statement[0] in (
        "boolean",
        "common",
        "dimension",
        "external_function",
        "format_variable",
        "integer",
        "floating_point",
        "normal",
        "parameter",
        "restore_return",
        "save_return",
        "statement_label",
        "insert_file",
        "values",
    ):
        pass
    elif statement[0] == "internal_function":
        return "\nstatic"
    elif statement[0] == "continue":
        return "/* continue */;"
    elif statement[0] in ("end_program", "end_function", "end_conditional"):
        rep = "}"
        if statement[0] == "end_function":
            if termbraceemit == 0:
                rep = ""
            else:
                termbraceemit -= 1
            rep += "\n"
        if mad_debug:
            rep += " /* %s */" % statement[0]
        return rep
    elif statement[0] == "else":
        return "} else {"
    elif statement[0] == "entry":
        rep = ""
        # if not hasmain:
        if termbraceemit > 0:
            rep = "}\n"
            termbraceemit = 0
        funcname = statement[1]
        currmode = normalmode
        if currmode == "default":
            currmode = "double"
        rep += returntypes.get(funcname, "void") + " " + funcname[:-1] + "("
        defaulted = []
        if funcname == "main.":
            rep = rep + "int argc, char **argv"
        else:
            # print "entry: ", statement
            funcargs = ()
            if len(statement) > 2:
                funcargs = statement[2]
                for vtype, var in statement[2]:
                    if vtype == "funptr":
                        # we lose if a passed-in function has a type other than the
                        # normal mode -- there's no way to deduce this.
                        rep += "%s (*%s)()" % (currmode, var)
                    elif vtype == "variable":
                        if var in stmtlabels:
                            declarator = "*" + var
                        elif var in dimensions:
                            declarator = "*" + var
                        elif var in assigntargets:
                            declarator = "*" + var
                        else:
                            declarator = var
                        if var in typedict:
                            if typedict[var] == "default":
                                typedict[var] = currmode
                            rep += typedict[var] + " " + declarator + ", "
                            del typedict[var]
                        else:
                            rep += currmode + " " + declarator + ", "
                            defaulted.append(var)
                if rep.endswith(", "):
                    rep = rep[:-2]
        rep = rep + ")\n{"
        termbraceemit += 1
        if funcname == "main." and ctssmode:
            rep += "\n\tCtssArgc = argc; CtssArgv = argv;"
        return rep
    elif statement[0] == "error_return":
        return 'puts("ERROR RETURN from line %s:%d"); exit(1);' % (
            sourcefile,
            sourceline,
        )
    elif statement[0] == "execute":
        return print_C_expr(statement[1]) + ";"
    elif statement[0] == "external_call":
        return "\n" + print_C_expr(("apply", statement[1], statement[2])) + ";"
    elif statement[0] == "for":
        (keyword, label, dummy, start, increment, predicate) = statement
        rep = "for (%s = %s; !(%s); %s += %s) {" % (
            dummy,
            print_C_expr(start),
            print_C_expr(predicate),
            dummy,
            print_C_expr(increment),
        )
        if mad_debug:
            rep += " /* label %s */" % label
        return rep
    elif statement[0] == "switch":
        # print "switchtables: ", switchtables
        # print "stmt: ", statement
        table = switchtables[statement[1]]
        # print "table ", table
        index = 1
        swval = print_C_expr(statement[2])
        rep = "switch (" + swval + ") {\n"
        for vtype, label in table:
            rep += "\tcase " + str(index) + ": goto " + label + ";\n"
            index += 1
        rep += (
            '\tdefault: fprintf(stderr, "TRANSFER target undefined: '
            + swval
            + '=%ld\\n",'
            + swval
            + "); exit(1);\n\t}"
        )
        return rep
    elif statement[0] == "goto":
        if statement[1] in stmtlabels:
            rep = statement[1]
            # print "goto: funcargs: ", funcargs
            # print "goto: statement: ", statement
            # print "funcname: ", funcname
            varfound = False
            for vtype, var in funcargs:
                if rep == var:
                    varfound = True
                    break
            if varfound:
                retval = ""
                if funcname in returntypes:
                    if returntypes[funcname] == "int64_t":
                        retval = "0LL"
                    elif returntypes[funcname] == "bool":
                        retval = "False"
                    else:
                        retval = "0.0"
                rep = "*" + rep
                return "{ " + rep + " = 1L; return " + retval + "; }"
        return "goto " + statement[1] + ";"
    elif statement[0] == "if":
        return (
            "if (" + print_C_expr(statement[1]) + ") " + print_C_statement(statement[2])
        )
    elif statement[0] == "internal":
        currmode = normalmode
        if currmode == "default":
            currmode = "double"
        rep = returntypes.get(statement[1], currmode) + " " + statement[1][:-1] + "("
        for dummy, var in statement[2]:
            rep += mode((dummy, var)) + " " + var + ", "
        rep = rep[:-2]
        rep += ") {return %s;}" % print_C_expr(statement[3])
        return rep
    elif statement[0] == "map":
        global dummycount
        count = len(statement[3])
        dummy = "d_%d" % dummycount
        dummycount += 1
        rep = "for (int %s = 0; %s < %d; %s++) {%s = " % (
            dummy,
            dummy,
            count,
            dummy,
            statement[2],
        )
        rep += "((%s [%d]){%s})[%s];" % (
            statement[3][0][0][:-3],
            count,
            ", ".join([str(x[1]) for x in statement[3]]),
            dummy,
        )
        return rep
    elif statement[0] == "or":
        return "} else if (" + print_C_expr(statement[1]) + ") {"
    elif statement[0] == "otherwise":
        return "else {"
    elif statement[0] == "pause":
        return (
            '{printf("PAUSE %lo (Y to start):", '
            + print_C_expr(statement[1])
            + "); while (toupper(getchar()) != 'Y') ;}"
        )
    elif statement[0] == "print_comment":
        return 'puts("' + lpt_codes(statement[1]) + '");'
    elif statement[0] == "print_results":
        fmt = 'printf("'
        for var in statement[1]:
            fmt += var.upper() + " = "
            if typedict[var] == "int64_t":
                fmt += "%ld, "
            elif typedict[var] == "double":
                fmt += "%f, "
            elif typedict[var] == "bool":
                fmt += "%dB, "
        fmt = fmt[:-2]
        fmt += r'\n", '
        for var in statement[1]:
            if typedict[var] == "int64_t":
                fmt += "(long)"
            fmt += var
            fmt += ", "
        fmt = fmt[:-2] + ");"
        return fmt
    elif statement[0] in ("print", "print_online"):
        if statement[0] == "print":
            file = "stdout"
        else:
            file = "stderr"
        (fmttype, fmtval) = statement[1]
        if fmttype == "char":
            fmt = fmt_convert(fmtval)
            fmtval = '"' + fmtval + '"'
        else:
            if fmtval in vecvalues:
                fmt = vecvalues[fmtval]
                (ft, fmt) = fmt[0]
                fmt = fmt_convert(fmt)
        rep = "{\n\tProcessFmt(FmtVars, Fmt, " + fmtval + ", False, True);\n"
        if len(statement) < 3:
            rep += "\tfputs(Fmt, " + file + ");\n"
        else:
            rep += (
                "\tfprintf("
                + file
                + ", Fmt, "
                + ", ".join(map(print_C_expr, statement[2:]))
                + ");\n"
            )
        rep += "\t}"
        return rep
    elif statement[0] == "writebcd":
        lun = str(statement[1][1])
        file = "Tape_" + lun
        tapeloc = "TapeLocation_" + lun
        (fmttype, fmtval) = statement[2]
        if fmttype == "char":
            fmt = fmt_convert(fmtval)
            fmtval = '"' + fmtval + '"'
        else:
            if fmtval in vecvalues:
                fmt = vecvalues[fmtval]
                (ft, fmt) = fmt[0]
                fmt = fmt_convert(fmt)
        rep = "{\n\t" + file + " = TapeOpen(" + file + ', "w", ' + lun + ");\n"
        rep += "\tProcessFmt(FmtVars, Fmt, " + fmtval + ", False, True);\n"
        rep += "\t" + tapeloc + " = ftell(" + file + ");\n"
        if len(statement) < 4:
            rep += "\tfputs(Fmt, " + file + ");\n"
        else:
            rep += (
                "\tfprintf("
                + file
                + ", Fmt, "
                + ", ".join(map(print_C_expr, statement[3:]))
                + ");\n"
            )
        rep += "\t}"
        return rep
    elif statement[0] == "writebin":
        lun = str(statement[1][1])
        file = "Tape_" + lun
        tapeloc = "TapeLocation_" + lun
        rep = (
            "{\n\tint TapeNdx = 0;\n\t"
            + file
            + " = TapeOpen("
            + file
            + ', "w", '
            + lun
            + ");\n"
        )
        if len(statement[2:]) > maxtapevectors:
            fatal("Too many arguments for WRITE BINARY TAPE")
        for var in statement[2:]:
            # print "var: ", var
            # print "mode: ", deduce_expr(var)
            expmode = deduce_expr(var)
            if expmode == "int64_t":
                rep += (
                    "\tTapeWriteVars[TapeNdx].intv = (int64_t)("
                    + print_C_expr(var)
                    + ");\n"
                )
            elif expmode == "bool":
                rep += (
                    "\tTapeWriteVars[TapeNdx].boolv = (bool)("
                    + print_C_expr(var)
                    + ");\n"
                )
            elif expmode == "double":
                rep += (
                    "\tTapeWriteVars[TapeNdx].dblv = (double)("
                    + print_C_expr(var)
                    + ");\n"
                )
            rep += "\tTapeVectors[TapeNdx].ptr = &TapeWriteVars[TapeNdx];\n"
            rep += "\tTapeVectors[TapeNdx].len = sizeof(TapeWriteVars[TapeNdx]);\n"
            rep += "\tTapeNdx++;\n"
        rep += (
            "\tTapeWriteBinary(TapeVectors, "
            + file
            + ", "
            + lun
            + ", TapeNdx, &"
            + tapeloc
            + ");\n"
        )
        rep += "\t}"
        return rep
    elif statement[0] == "readbcd":
        lun = str(statement[1][1])
        file = "Tape_" + lun
        tapeloc = "TapeLocation_" + lun
        (fmttype, fmtval) = statement[2]
        if fmttype == "char":
            fmt = fmt_convert(fmtval)
            fmtval = '"' + fmtval + '"'
            types = deduce_types(fmt)
        else:
            if fmtval in vecvalues:
                fmt = vecvalues[fmtval]
                (ft, fmt) = fmt[0]
                fmt = fmt_convert(fmt)
                types = deduce_types(fmt)
            else:
                types = []
        inlist = []
        if len(statement[3:]) != len(types):
            fatal(
                "READ BCD TAPE format/argument count mismatch: %d format fields, %d args."
                % (len(types), len(statement[3:]))
            )
        for ispec, itype in zip(statement[3:], types):
            if type(ispec) == type(""):
                inlist.append("&" + ispec)
            elif type(ispec) == type(()) and ispec[0] == "range" and itype == "char":
                inlist.append(print_C_expr(ispec))
            else:
                nonfatal("Unsupported type in READ BCD TAPE:" + repr(ispec))
        rep = "{\n\t" + file + " = TapeOpen(" + file + ', "r", ' + lun + ");\n"
        rep += "\tProcessFmt(FmtVars, Fmt, " + fmtval + ", True, True);\n"
        rep += "\t" + tapeloc + " = ftell(" + file + ");\n"
        rep += (
            "\tif ((ScanCnt = fscanf("
            + file
            + ", Fmt, "
            + ", ".join(inlist)
            + ")) != "
            + str(len(inlist))
            + ") {\n"
        )
        rep += "\t   if (ScanCnt != EOF) \n"
        rep += '\t      fprintf (stderr, "Conversion error: FORMAT = %s\\n", ErrFmt);\n'
        if tapeeofvector != "":
            rep += "\t   else \n"
            rep += "\t      goto " + tapeeofvector + ";\n"
        rep += "\t   exit(1);\n"
        rep += "\t   }\n"
        rep += "\t}"
        return rep
    elif statement[0] == "readbin":
        lun = str(statement[1][1])
        file = "Tape_" + lun
        tapeloc = "TapeLocation_" + lun
        rep = (
            "{\n\tint TapeNdx = 0;\n\t"
            + file
            + " = TapeOpen("
            + file
            + ', "r", '
            + lun
            + ");\n"
        )
        if len(statement[2:]) > maxtapevectors:
            fatal("Too many arguments for READ BINARY TAPE")
        for var in statement[2:]:
            rep += "\tTapeVectors[TapeNdx].ptr = &" + var + ";\n"
            rep += "\tTapeVectors[TapeNdx++].len = sizeof(" + var + ");\n"
        if tapeeofvector == "":
            rep += (
                "\tTapeReadBinary(TapeVectors, "
                + file
                + ", "
                + lun
                + ", TapeNdx, &"
                + tapeloc
                + ", NULL);\n"
            )
        else:
            rep += (
                "\tTapeReadBinary(TapeVectors, "
                + file
                + ", "
                + lun
                + ", TapeNdx, &"
                + tapeloc
                + ", &"
                + tapeeofvector
                + ");\n"
            )
            rep += "\tif (" + tapeeofvector + " != 0LL) goto " + tapeeofvector + ";\n"
        rep += "\t}"
        return rep
    elif statement[0] == "read":
        (fmttype, fmtval) = statement[1]
        if fmttype == "char":
            fmt = fmt_convert(fmtval)
            fmtval = '"' + fmtval + '"'
            types = deduce_types(fmt)
        else:
            if fmtval in vecvalues:
                fmt = vecvalues[fmtval]
                (ft, fmt) = fmt[0]
                fmt = fmt_convert(fmt)
                types = deduce_types(fmt)
            else:
                types = []
        inlist = []
        if len(statement[2:]) != len(types):
            fatal(
                "READ format/argument count mismatch: %d format fields, %d args."
                % (len(types), len(statement[2:]))
            )
        for ispec, itype in zip(statement[2:], types):
            if type(ispec) == type(""):
                inlist.append("&" + ispec)
            elif type(ispec) == type(()) and ispec[0] == "range" and itype == "char":
                inlist.append(print_C_expr(ispec))
            else:
                nonfatal("Unsupported type in READ FORMAT:" + repr(ispec))
        rep = "{\n\tProcessFmt(FmtVars, Fmt, " + fmtval + ", True, True);\n"
        rep += (
            "\tif ((ScanCnt = scanf(Fmt, "
            + ", ".join(inlist)
            + ")) != "
            + str(len(inlist))
            + ") {\n"
        )
        rep += "\t   if (ScanCnt != EOF)\n"
        rep += '\t      fprintf (stderr, "Conversion error: FORMAT = %s\\n", ErrFmt);\n'
        rep += "\t   exit(1);\n"
        rep += "\t   }\n"
        rep += "\t}"
        return rep
    elif statement[0] == "read_and_print_data":
        return "ReadPrintData(datamap, False);"
    elif statement[0] == "read_data":
        return "ReadPrintData(datamap, True);"
    elif statement[0] == "backspacetape":
        lun = str(statement[1][1])
        file = "Tape_" + lun
        tapeloc = "TapeLocation_" + lun
        return "TapeBackspace(" + file + ", " + lun + ", &" + tapeloc + ");"
    elif statement[0] == "endfiletape":
        lun = str(statement[1][1])
        file = "Tape_" + lun
        tapeloc = "TapeLocation_" + lun
        return file + " = TapeClose(" + file + ", " + lun + ", 0);"
    elif statement[0] == "rewindtape":
        lun = str(statement[1][1])
        file = "Tape_" + lun
        tapeloc = "TapeLocation_" + lun
        return "TapeRewind(" + file + ", " + lun + ", &" + tapeloc + ");"
    elif statement[0] == "unloadtape":
        lun = str(statement[1][1])
        file = "Tape_" + lun
        tapeloc = "TapeLocation_" + lun
        return file + " = TapeClose(" + file + ", " + lun + ", 1);"
    elif statement[0] == "remark":
        if len(statement) == 1:
            return "\n"
        else:
            return "/* %s */" % statement[1].strip()
    elif statement[0] == "restore":
        rep = ""
        popoff = list(statement[1])
        popoff.reverse()
        currmode = normalmode
        if currmode == "default":
            currmode = "double"
        rep += "{CheckRecursorPop(%d); " % len(popoff)
        for var in popoff:
            rep += "%s = (%s)(*--recursor); " % (var, typedict.get(var, currmode))
        rep += "}"
        return rep
    elif statement[0] == "return":
        if len(statement) == 1:
            return "return;"
        else:
            return "return %s;" % print_C_expr(statement[1])
    elif statement[0] == "save":
        currmode = normalmode
        if currmode == "default":
            currmode = "double"
        rep = ""
        rep += "{CheckRecursorPush(%d); " % len(statement[1])
        for expr in statement[1]:
            rep += "*recursor++ = (%s)(%s); " % (currmode, print_C_expr(expr))
        rep += "}"
        return rep
    elif statement[0] == "set_list_to":
        pass
    elif statement[0] == "substitution":
        savemode = ctssmode
        ctssmode = False
        rep = print_C_expr(statement[1])
        ctssmode = savemode
        varfound = False
        # print "funcargs: ", funcargs
        for vtype, var in funcargs:
            if rep == var:
                varfound = True
                break
        if varfound and not rep.endswith("]"):
            rep = "*" + rep
        return rep + " = " + print_C_expr(statement[2]) + ";"
    elif statement[0] == "whenever":
        return "if (" + print_C_expr(statement[1]) + ") {"
    else:
        return "// " + repr(statement)


def generate_C_code(out: TextIO) -> None:
    global funcname
    funcname = ""
    out.write("/* generated by the MAD compiler */\n")
    for header in headers:
        out.write("#include <%s.h>\n" % header)
    if headers:
        out.write("\n")
    if ctssmode:
        out.write("#include <ctss.h>\n\n")
    if not ctssmode:
        out.write("typedef int64_t bool;\n#define True 1\n#define False 0\n\n")

    # Output PARAMETERs
    if parameters:
        # print "parameters = ", parameters
        for id, value in list(parameters.items()):
            if id in typedict:
                del typedict[id]
            out.write("#define %s %s\n" % (id.upper(), value))
        out.write("\n")

    # Generate indexing macros for MAD's screwy dope vectors
    depths = []
    for d in list(arraydepths.values()):
        if d > 1 and d not in depths:
            depths.append(d)
    for i in depths:
        out.write("#define MAD%d(d, " % i)
        out.write(", ".join(["i%d" % (x + 1) for x in range(i)]))
        out.write(")        d[1] + ")
        term = ""
        for j in range(i):
            term += "("
            for k in range(i - j - 1):
                term += "d[%d]*" % (k + 2)
            if j < i - 1:
                term += "*"
            term = term[:-1] + "((i%d)-1))" % (j + 1)
            term += " + "
        term = term[:-3]
        out.write(term + "\n")

    # Generate VECTOR VALUES assignments.
    # May be local(static) or in common(global).
    if len(vecvalues) > 0:
        for id, value in list(vecvalues.items()):
            vtype = vecvalues[id][0][0]
            if id in common:
                stmode = ""
            else:
                stmode = "static "
            if vtype == "char":
                if id in dimensions:
                    out.write(
                        "%schar *%s[%s] = {%s};\n"
                        % (
                            stmode,
                            id,
                            dimensions[id][0],
                            ", ".join([x[1] for x in value]),
                        )
                    )
                else:
                    fmt = vecvalues[id][0][1]
                    out.write("%schar *%s = %s;\n" % (stmode, id, fmt))
            elif vtype == "floatnum" or vtype == "default":
                if id in dimensions:
                    out.write(
                        "%sdouble %s[%s] = {%s};\n"
                        % (
                            stmode,
                            id,
                            dimensions[id][0],
                            ", ".join([x[1] for x in value]),
                        )
                    )
                else:
                    out.write(
                        "%sdouble %s = {%s};\n"
                        % (stmode, id, ", ".join([x[1] for x in value]))
                    )
            elif vtype == "intnum":
                if id in dimensions:
                    out.write(
                        "%sint64_t %s[%s] = {%s};\n"
                        % (
                            stmode,
                            id,
                            dimensions[id][0],
                            ", ".join([x[1] for x in value]),
                        )
                    )
                else:
                    out.write(
                        "%sint64_t %s = {%s};\n"
                        % (stmode, id, ", ".join([x[1] for x in value]))
                    )
            elif vtype == "boolnum":
                val = str(vecvalues[id][0][1])
                if id in dimensions:
                    out.write(
                        "%sbool %s[%s] = {%s};\n" % (stmode, id, dimensions[id][0], val)
                    )
                else:
                    out.write("%sbool %s = {%s};\n" % (stmode, id, val))
            elif id in stmtlabels:
                # print "VEC VAR stmtlabels", stmtlabels
                # print "   stmt:", vecvalues[id]
                vtype = "label"  # Dummy this out as we process elsewhere.
            else:
                sys.stderr.write("mad: VECTOR VALUES %s has unknown type\n" % id)
                sys.exit(1)
        out.write("\n")

    # Generate SET LIST controls.
    if len(set_lists) > 0:
        args = ""
        for i in range(len(set_lists)):
            (var, args) = set_lists[i]
            if var in typedict:
                del typedict[var]
            dim = dimensions[var]
            del dimensions[var]
            currmode = normalmode
            if currmode == "default":
                currmode = "double"
            rep = ""
            rep += "%s %s[%s], *recursor = %s;\n" % (currmode, var, dim[0], var)
            out.write(rep)
        out.write("int recursorcnt = 0;\n")
        out.write("int recursorlen = " + print_C_expr(args) + ";\n\n")

    # Generate TAPE controls.
    if len(tapeluns) > 0:
        # print "tapeluns: ", tapeluns
        if not ctssmode:
            out.write("typedef struct {void *ptr; int len;} TapeVector;\n")
            out.write(
                "typedef union {int64_t intv; bool boolv; double dblv;} TapeVars;\n"
            )
        if hasmain:
            out.write("TapeVector TapeVectors[" + str(maxtapevectors) + "];\n")
            out.write("TapeVars TapeWriteVars[" + str(maxtapevectors) + "];\n")
            for var, lun in list(tapeluns.items()):
                out.write("FILE *" + var + " = NULL;\n")
                out.write("long TapeLocation_" + str(lun[1]) + " = 0;\n")
        else:
            out.write("extern TapeVector TapeVectors[" + str(maxtapevectors) + "];\n")
            out.write("extern TapeVars TapeWriteVars[" + str(maxtapevectors) + "];\n")
            for var, lun in list(tapeluns.items()):
                out.write("extern FILE *" + var + ";\n")
                out.write("extern long TapeLocation_" + str(lun[1]) + ";\n")
        out.write("\n")

    # Output common variables
    # we output as global variables if hasmain is True
    # otherwise, as extern variables
    declarations = ""
    if common:
        out.write("\n")
        for vtype in ("double", "int64_t", "bool", "char", "default"):
            vars = [x for x in list(typedict.keys()) if typedict[x] == vtype]
            vars = [x for x in vars if x not in vecvalues]
            vars = [x for x in vars if x in common]
            if vars:
                # print "common: vtype = %s, mode = %s" % (vtype, normalmode)
                # print "vars = ", vars
                if vtype == "default":
                    if vtype != normalmode:
                        vtype = normalmode
                    else:
                        vtype = "double"
                # print "newvtype = %s, mode = %s" % (vtype, normalmode)
                if hasmain:
                    declarations += vtype
                else:
                    declarations += "extern " + vtype
                for var in vars:
                    if var in common:
                        declarations += " " + var
                        if common[var][0] != "0":
                            declarations += "[" + common[var][0] + "]"
                        declarations += ","
                        del typedict[var]
                declarations = declarations[:-1] + ";\n"
        if declarations:
            out.write(declarations[:-1])
        out.write("\n\n")

    if hasmain and ctssmode:
        out.write("int CtssArgc;\n")
        out.write("char **CtssArgv;\n\n")

    # Emit recursor definition if used.
    if not hasmain and emitcursor:
        currmode = normalmode
        if currmode == "default":
            currmode = "double"
        out.write("extern %s *recursor;\n" % currmode)
        out.write("extern void CheckRecursorPush(int);\n")
        out.write("extern void CheckRecursorPop(int);\n\n")

    # Output local (static) variables
    declarations = ""
    for vtype in ("double", "int64_t", "bool", "char", "default"):
        vars = [x for x in list(typedict.keys()) if typedict[x] == vtype]
        vars = [x for x in vars if x not in vecvalues]
        if vars:
            # print "vtype = %s, mode = %s" % (vtype, normalmode)
            # print "vars = ", vars
            if vtype == "default":
                if vtype != normalmode:
                    vtype = normalmode
                else:
                    vtype = "double"
            # print "newvtype = %s, mode = %s" % (vtype, normalmode)
            declarations += "static " + vtype
            for var in vars:
                if typedict[var] == "default":
                    typedict[var] = vtype
                declarations += " " + var
                if var in dimensions:
                    declarations += "[" + dimensions[var][0] + "]"
                declarations += ","
            declarations = declarations[:-1] + ";\n"
    if declarations:
        out.write(declarations[:-1])
    out.write("\n")

    global emitscanf
    if emitscanf:
        out.write("static int ScanCnt;\n\n")

    global emitpackstr
    if not ctssmode and emitpackstr:
        out.write(packstr)

    out.write("static char Fmt[256];\n\n")
    global emitformatter
    if not ctssmode:
        out.write("\ntypedef struct { char *var; int64_t *loc;} FmtVar;\n")
    if emitformatter and not ctssmode:
        out.write("static char ErrFmt[256];\n\n")
        if hasmain:
            out.write(formatter)
        else:
            out.write(
                "extern void ProcessFmt (FmtVar *fmtvar, char *cfmt, char *madfmt, int read, int first);\n"
            )

    if emittapeio and not ctssmode:
        out.write(taperoutines)

    if emitreadprint:
        if hasmain:
            out.write(readprintdata % globals())
            out.write("\nstatic struct datamap_t datamap[] = {\n")
            for v, t in list(typedict.items()):
                out.write('    {.name = "%s", ' % v)
                if t == "int64_t":
                    out.write(".type = INT, .val.intp = &%s},\n" % v)
                elif t == "double":
                    out.write(".type = FLOAT, .val.floatp = &%s},\n" % v)
                elif t == "bool":
                    out.write(".type = BOOL, .val.boolp = &%s},\n" % v)
            out.write("    {.name = NULL, .type = INT, .val.intp = NULL}\n")
            out.write("};\n")
        else:
            out.write(
                "extern void ReadPrintData (struct datamap_t *map, int readonly);\n\n"
            )

    if hasmain and emitchkcursor:
        out.write(checkrecursor)

    # Output FORMAT VARIABLE array
    out.write("static FmtVar FmtVars[] = {\n")
    if formatvars:
        # print "formatvars = ", formatvars
        for id, value in list(formatvars.items()):
            out.write('   {"%s", &%s},\n' % (id.upper(), value))
    out.write('   {"",(void *)0}\n')
    out.write("};\n\n")

    # Generate internal function templates
    if len(internalfunctions) > 0:
        currmode = normalmode
        if currmode == "default":
            currmode = "double"
        for i in range(len(internalfunctions)):
            (currentfunc, args) = internalfunctions[i]
            for j in range(len(executefunctions)):
                (execfunc, execargs) = executefunctions[j]
                if currentfunc == execfunc:
                    executefunctions.pop(j)
                    break
            rep = (
                "static "
                + returntypes.get(currentfunc, "void")
                + " "
                + currentfunc[:-1]
                + "("
            )
            if len(args) == 0:
                rep = rep + "void"
            else:
                for j in range(len(args)):
                    (vtype, var) = args[j]
                    if var in stmtlabels:
                        declarator = " *"
                    elif var in dimensions:
                        declarator = " *"
                    elif var in assigntargets:
                        declarator = " *"
                    else:
                        declarator = ""
                    if var in typedict:
                        if typedict[var] == "default":
                            typedict[var] = "double"
                        rep += typedict[var] + declarator + ", "
                    else:
                        rep += currmode + " " + declarator + ", "
                if rep.endswith(", "):
                    rep = rep[:-2]
            rep = rep + ");\n"
            out.write(rep)
        out.write("\n")

    # Generate external function templates
    if len(executefunctions) > 0:
        currmode = normalmode
        if currmode == "default":
            currmode = "double"
        for i in range(len(executefunctions)):
            (currentfunc, args) = executefunctions[i]
            # print "execute: function = %s, len(args) = %d" % (currentfunc, len(args))
            # print "   args = ", args
            # print "   returntype = ", returntypes.get(currentfunc, currmode)
            if ctssmode and currentfunc in ctssfuncs:
                continue
            if currentfunc in tapefuncs:
                continue
            if currentfunc in internalfunctions:
                continue
            rep = returntypes.get(currentfunc, currmode) + " " + currentfunc[:-1] + "("
            if len(args) == 0:
                rep = rep + "void"
            else:
                for j in range(len(args)):
                    # print "   args[%d]: len(arg) = %d" % (j, len(args[j]))
                    # print "       ", args[j]
                    if len(args[j]) > 2:
                        (vtype, var, index) = args[j]
                    else:
                        (vtype, var) = args[j]
                    declarator = ""
                    if var in stmtlabels:
                        rep += "int64_t *, "
                    elif var in typedict:
                        if vtype == "variable" and typedict[var] == "char":
                            if ctssmode:
                                typedict[var] = "int64_t"
                            else:
                                declarator = " *"
                        if var in dimensions:
                            declarator = " *"
                        else:
                            declarator = ""
                        if typedict[var] == "default":
                            typedict[var] = "double"
                        rep += typedict[var] + declarator + ", "
                    else:
                        rep += currmode + declarator + ", "
                if rep.endswith(", "):
                    rep = rep[:-2]
            rep = rep + ");\n"
            out.write(rep)
        out.write("\n")

    indent = 0
    global sourcefile, sourceline
    scopes = []  # THROUGH scopes that are pending
    for sourcefile, sourceline, label, statement in deck:
        if label in targets:
            out.write("  " + label + ":\n")
        if statement[0] in ("for", "map"):
            scopes.append(statement[1])
        try:
            rep = print_C_statement(statement)
        except:
            nonfatal("Internal error while generating code")
            (exc_type, exc_value, exc_traceback) = sys.exc_info()
            raise exc_type(exc_value).with_traceback(exc_traceback)
        if rep:
            cnt = 0
            for i in range(len(rep)):
                if rep[i] == "}":
                    cnt -= 1
                elif rep[i] == "{":
                    cnt += 1
            if cnt < 0 or rep[0] == "}":
                indent -= 1
            rep = re.sub("\t", ("    " * (indent + 1)), rep)
            out.write("    " * indent + rep + "\n")
            if cnt > 0 or rep[-1] == "{":
                indent += 1
        while scopes and label == scopes[-1]:
            out.write(("    " * indent) + "}")
            if mad_debug:
                out.write(("    " * indent) + " /* closing " + label + " */")
            out.write("\n")
            indent -= 1
            scopes.pop()


def usage() -> None:
    sys.stderr.write(
        "Usage: mad [-c][-C][-d][-g][-I][-l][-L][-S] [-o target] [file...]\n"
    )


if __name__ == "__main__":
    import os, getopt

    mad_debug = 0
    binary = True
    lexercise = False
    sourceout = False
    dump_tuples = False
    libdirs = ""
    incldirs = ""
    libs = ""
    target = None
    try:
        (options, arguments) = getopt.getopt(sys.argv[1:], "cd:eghl:o:vCI:L:S")
    except getopt.GetoptError as err:
        print(str(err))
        usage()
        sys.exit(1)
    for opt, val in options:
        if opt == "-c":
            binary = False
        elif opt == "-d":
            mad_debug = int(val)
        elif opt == "-e":
            lexercise = True
        elif opt == "-g":
            dump_tuples = True
        elif opt == "-h":
            usage()
            sys.exit(1)
        elif opt == "-l":
            libs += " " + opt + val
        elif opt == "-o":
            target = val
        elif opt == "-v":
            verbose += 1
        elif opt == "-C":
            ctssmode = True
            if ctssdirs != "/usr/include":
                libdirs += "-L" + ctssdirs
            incldirs += "-I" + ctssdirs
            libs += " -lctss"
        elif opt == "-I":
            incldirs += opt + val + " "
        elif opt == "-L":
            libdirs += opt + val + " "
        elif opt == "-S":
            sourceout = True
            binary = False

    if len(arguments) == 0:
        arguments = ["-"]

    # Run each mad module through the translator
    firstmodule = True
    origargs = arguments
    for file in arguments:
        if not firstmodule:
            errcount = 0
            refcount = {}
            sourcefile = ""
            sourceline = 0
            deck = []
            targets = {}
            stmtlabels = {}
            forlabels = {}
            switchtables = {}
            headers = ["stdint"]
            dimensions = {}
            set_lists = []
            arraydepths = {}
            returntypes = {"main.": "int"}
            vecvalues = {}
            common = {}
            format = {}
            parameters = {}
            formatvars = {}
            hasmain = False
            packstrings = True
            emitpackstr = False
            emitscanf = False
            termbraceemit = 0
            emitformatter = False
            emitreadprint = False
            emitcursor = False
            emitchkcursor = False
            emitargs = False
            emittapeio = False
            internalfunctions = []
            executefunctions = []
            assigntargets = {}
            typedict = {}
            normalmode = "default"
            dummycount = 0
            tapeluns = {}
            tapeeofvector = ""

        insertfile = False
        if file == "-" or file.endswith(".mad"):
            if file == "-":
                sourcefile = "stdin"
                readstdin = True
            else:
                sourcefile = file
                fp = open(file)
                readstdin = False
            lexer.lineno = 1
            source = ""
            while True:
                if insertfile:
                    line = ifp.readline().expandtabs()
                elif readstdin:
                    line = sys.stdin.readline().expandtabs()
                else:
                    line = fp.readline().expandtabs()
                if line == "":
                    if insertfile:
                        ifp.close()
                        insertfile = False
                        sourcefile = file
                        continue
                    break
                label = ""
                contfield = ""
                statement = ""
                # print "line: len = ", len(line)
                # print line
                if line[0] == "\n":
                    continue
                if len(line) == 81 and line[-5:-1].isdigit():
                    llen = -9
                else:
                    llen = -1
                if len(line) >= 11:
                    label = line[:9].strip()
                    contfield = line[10]
                    if len(line) > 11:
                        statement = line[11:llen]
                if contfield.isdigit():
                    source = re.sub(" eos\n$", statement + " eos\n", source)
                elif contfield == "R":
                    source += "| COMMENT $" + statement + "$ eos\n"
                else:
                    source += label + "|" + statement + " eos\n"
                statement = re.sub("INSERT +FILE", "INSERT_FILE", statement.strip())
                if contfield != "R" and statement[:11] == "INSERT_FILE":
                    sourcefile = statement[12:].strip().lower() + ".mad"
                    # print "statement: %s, file: %s" % (statement, sourcefile)
                    ifp = open(sourcefile)
                    insertfile = True
            if not readstdin:
                fp.close()
        elif file.endswith(".c") or file.endswith(".o"):
            continue
        else:
            sys.stderr.write("mad: bad file extension on %s\n" % file)
            sys.exit(1)

        # Transform some adjacent groups of keywords into
        # agglomerates. This lets us get away with stuff like SET LIST
        # TO LIST or RETURN that the parser would otherwise choke on, because
        # the keywords have been lifted out of where they can collide
        # with identifier space.
        source = re.sub("SET +LIST +TO", "SET_LIST_TO", source)
        source = re.sub("PRINT +FORMAT", "PRINT_FORMAT", source)
        source = re.sub("PRINT +ON +LINE +FORMAT", "PRINT_ONLINE_FORMAT", source)
        source = re.sub("PRINT +COMMENT", "PRINT_COMMENT", source)
        source = re.sub("PRINT +RESULTS", "PRINT_RESULTS", source)
        source = re.sub("READ +AND +PRINT +DATA", "READ_AND_PRINT_DATA", source)
        source = re.sub("READ +DATA", "READ_DATA", source)
        source = re.sub("READ +FORMAT", "READ_FORMAT", source)
        source = re.sub("PROGRAM +COMMON", "PROGRAM_COMMON", source)
        source = re.sub("FLOATING +POINT", "FLOATING_POINT", source)
        source = re.sub("SAVE +RETURN", "SAVE_RETURN", source)
        source = re.sub("ERROR +RETURN", "ERROR_RETURN", source)
        source = re.sub("RESTORE +RETURN", "RESTORE_RETURN", source)
        source = re.sub("FUNCTION +RETURN", "FUNCTION_RETURN", source)
        source = re.sub("FORMAT +VARIABLE", "FORMAT_VARIABLE", source)
        source = re.sub("STATEMENT +LABEL", "STATEMENT_LABEL", source)
        source = re.sub("WRITE +BCD +TAPE", "WRITE_BCD_TAPE", source)
        source = re.sub("WRITE +BINARY +TAPE", "WRITE_BINARY_TAPE", source)
        source = re.sub("READ +BCD +TAPE", "READ_BCD_TAPE", source)
        source = re.sub("READ +BINARY +TAPE", "READ_BINARY_TAPE", source)
        source = re.sub("END +OF +FILE +TAPE", "END_OF_FILE_TAPE", source)
        source = re.sub(
            "BACKSPACE +RECORD +OF +TAPE", "BACKSPACE_RECORD_OF_TAPE", source
        )
        source = re.sub("REWIND +TAPE", "REWIND_TAPE", source)
        source = re.sub("UNLOAD +TAPE", "UNLOAD_TAPE", source)
        source = re.sub("INSERT +FILE", "INSERT_FILE", source)
        # Clean up one seriously ugly feature of the syntax.
        source = re.sub(r"PAUSE\s*NO\.", "PAUSE", source)
        # print "source:\n%s" % source

        # Lex or parse
        if lexercise:
            # print "Calling lex"
            lex.input(source)
            while 1:
                tok = lex.token()
                if not tok:
                    break
                print("(%s,'%s',%d)" % (tok.type, tok.value, tok.lineno))
        else:
            # print "Calling yacc"
            try:
                yacc.parse(source, debug=mad_debug)
            except lex.LexError as e:
                pass

        os.system("rm -f parsetab.py")
        if lexercise:
            sys.exit(0)

        if not deck:
            sys.stderr.write("mad: empty deck\n")
            sys.exit(1)

        if dump_tuples:
            for t in deck:
                print(t)

        # Perform global analysis
        analyze()

        # Bring me the head of Alfred E. Neumann
        if errcount:
            if errcount >= alfiemin:
                print(alfie)
            sys.exit(1)

        # Might have been called as a filter
        if arguments == ["-"]:
            target = "-"
            generate_C_code(sys.stdout)
            sys.exit(0)

        stem = file[:-4]
        generated = stem + ".c"
        out = open(generated, "w")
        generate_C_code(out)
        out.close()
        firstmodule = False
        if "math" in headers:
            libs += " -lm"

    if sourceout:
        sys.exit(0)

    # Translation succeeded, run module(s) through C compiler
    arguments = origargs
    gccopts = os.environ.get("GCCOPTS", " ")
    status = 0
    for file in arguments:
        deletefile = False
        if file == "-":
            continue
        elif file.endswith(".mad"):
            generated = file[:-4] + ".c"
            object = file[:-4] + ".o"
            deletefile = True
        elif file.endswith(".c"):
            object = file[:-2] + ".o"
            generated = file
        else:
            continue

        # Must be gcc, else compilation of internal functions will fail
        ccmd = "gcc %s -std=c99 -o %s -c %s %s " % (
            gccopts,
            object,
            generated,
            incldirs,
        )
        if verbose:
            print(ccmd)
        status = os.system(ccmd)
        if deletefile:
            os.system("rm -f " + generated)
    if status != 0:
        sys.exit(status)

    # Compiles succeeded, run through C compiler to bind
    if binary:
        arguments = origargs
        objfiles = ""
        cleanfiles = ""
        for file in arguments:
            if file == "-":
                continue
            elif file.endswith(".mad"):
                stem = file[:-4]
                generated = stem + ".o"
                cleanfiles += generated + " "
            elif file.endswith(".c"):
                stem = file[:-2]
                generated = stem + ".o"
                cleanfiles += generated + " "
            else:
                stem = file[:-2]
                generated = file

            objfiles += generated + " "
            if not target:
                target = stem

        ccmd = "gcc %s -std=c99 -o %s %s %s %s" % (
            gccopts,
            target,
            objfiles,
            libdirs,
            libs,
        )
        if verbose:
            print(ccmd)
        status = os.system(ccmd)
        os.system("rm -f " + cleanfiles)
        if status != 0:
            os.system("rm -f " + target)
            sys.exit(status)
# end
