UOJ Logo tiger2005的博客

博客

UOJ #98.【集训队互测2015】未来程序·改 题解

2024-10-04 22:30:58 By tiger2005

原题链接 | 4ms 提交记录

上一版写的代码每跑一次函数都需要重新 parse 整个代码段,这实在是太蠢了,而且跑的也很慢。不过如果我们可以先生成字节码,就可以快速跑字节码了,非常方便。

我们首先实现一个虚拟机。

namespace VM {
const int MEM_SIZE = 1 << 22;
const int BYTE_CODE_LENGTH = 1 << 12;
i64 mem[MEM_SIZE];
i64 bytecode[BYTE_CODE_LENGTH];

int run(i64 start) {
  i64 *pc = (i64*)start, *sp = mem + MEM_SIZE, *bp = sp, ax, tmp;
  // finally execute _PSH and _EXIT by modifying pc
  *--sp = _EXIT;
  *--sp = _PSH;
  tmp = (i64)sp;
  *--sp = tmp;

  while (1) {
    i64 ins = *pc++;
    switch (ins) {
    case _LEA: ax = (i64)(bp + *pc++); break;
    case _LEAP: tmp = ax * (*pc++); ax = (*sp++) + tmp; break;
    case _IMM: ax = *pc++; break;
    case _JMP: pc = (i64*)*pc; break;
    case _JSR: *--sp = (i64)(pc + 1); pc = (i64*)(*pc); break;
    case _BZ: pc = ax ? pc + 1 : (i64*)(*pc); break;
    case _BNZ: pc = ax ? (i64*)(*pc) : pc + 1; break;
    case _ENT: *--sp = (i64)bp; bp = sp; break;
    case _ADJ: if (*pc < 0) memset(sp + *pc, 0, sizeof(i64) * -(*pc)); sp = sp + *pc++; break;
    case _LEV: sp = bp; bp = (i64*)*sp++; pc = (i64*)*sp++; break;
    case _LI: ax = *(i64*)ax; break;
    case _SI: *(i64*)*sp++ = ax; break;
    case _PSH: *--sp = ax; break;
    case _OR: ax = *sp++ || ax; break;
    case _XOR: ax = bool(*sp++) ^ bool(ax); break;
    case _AND: ax = *sp++ && ax; break;
    case _NOT: ax = !ax; break;
    case _EQ: ax = *sp++ == ax; break;
    case _NE: ax = *sp++ != ax; break;
    case _LT: ax = *sp++ < ax; break;
    case _GT: ax = *sp++ > ax; break;
    case _LE: ax = *sp++ <= ax; break;
    case _GE: ax = *sp++ >= ax; break;
    case _ADD: ax = *sp++ + ax; break;
    case _SUB: ax = *sp++ - ax; break;
    case _MUL: ax = *sp++ * ax; break;
    case _DIV: ax = *sp++ / ax; break;
    case _MOD: ax = *sp++ % ax; break;
    case _NEG: ax = -ax; break;
    case _SHL: printf("%d", (int)ax); sp++; break;
    case _SHR: *(i64*)ax = input_a[input_cur++]; sp++; break;
    case _PCHR: putchar(ax); sp++; break;
    case _EXIT: return (int)ax;
    }
  }
}
};

下面是字节码的设计。这个设计实际上使用了一些寄存器和一个栈,其中栈的低地址部分储存编译时确定的全局变量空间,而高位置部分储存函数调用信息和临时变量空间。我们需要同时在数组中储存指针和变量值,为了方便起见,这里统一使用 long long 处理这些值。这样的处理方案可能会导致一些操作符的行为出现异常,但是题目中给出的数据代码都非常正常,或者说不依赖于整形溢出之类的操作,所以不妨就假设这样处理没有问题。

为了跑动字节码,我们需要至少四个寄存器:

  • pc:指向当前字节码操作类型的指针,这个指针应当指向 bytecode 数组的一个位置;
  • sp:指向栈顶值的指针。在这个指针的帮助下,PUSH 操作等价于 *--sp = ...,而 POP 操作等价于 *sp++。需要注意的是,栈的方向是从高地址向低地址,那么指针的移动方向也需要以次为依据移动;
  • bp:指向栈底的指针。需要注意的是,bp 本身指向的内容并不是栈底的值,而是比栈底的值更高一个位置的值。这个值一般是上一个栈的栈底。在此基础上,我们就可以在退出函数时令 sp = bp, bp = *sp++ 快速回到函数被调用时的栈情况;
  • ax:临时的数值储存器。我们将会进一步讨论这个寄存器的作用。

我们观察表达式的求值部分,实际上其等价于在表达式树上以后序遍历的顺序计算每个子树的行为的过程。在平常的写法下,我们需要先把表达式子树的值压入栈中,然后调用根节点的操作进行运算。为了进一步减少对栈的操作,我们考虑如下策略:(下面的策略在双目操作符的表达式树上适用):

  • 计算左表达式树的值并自动储存在 ax 上;
  • ax 压入栈中;
  • 计算右表达式树的值并自动储存在 ax 上;
  • 这个时候操作的左值一定是栈顶的值,右值一定是 ax,调用双目操作对应的字节码,将左值弹出后与 ax 进行运算,将得到的结果继续储存在 ax 上返回。

这个方案很容易变成单目操作符的策略。而对于立即数,可以直接将对应的值加载到 ax 上。

接下来是字节码部分。

字节码 操作
LEA x 根据操作参数,以栈底为基址进行偏移,得到地址并储存在 ax 中。这个操作是为了在函数调用时准确的取到其局部变量的地址,因为在函数的规划下,每个局部变量和栈底的相对偏移是固定的。
LEAP x 取出栈顶值 ax',并令 ax = ax' + ax * x。这个部分主要是为了取出数组的地址,因为数组中每个位置的地址应当是每一维参数的加权和,而权重是可以在编译期计算的。
IMM x 将立即数 x 加载到 ax 上。
JMP p pc = p,也就是强制跳转。p 参数一般是以 pc 为基址的(因为多环境下字节码起始位置会发生变动),但是这一题中字节码储存在一个固定位置的数组中,所以可以忽略这一点。
JSR p pc + 1 压入栈后令 pc = p。这个操作可以和下面的 LEV 一起,让虚拟机在函数退出时回到正确的状态下。
BZ p 如果 ax 等于 0,则令 pc = p,否则令 pc = pc + 1。这本质上等于以 ax 为参数进行选择跳转。
BNZ p BZ p 的镜像版本。
ENT 进入函数主体前的工作。此时需要将 bp 压入栈中,然后令 bp = sp
LEV 函数调用结束。我们在函数调用时分别调用了 JSRENT,分别将 pc 的下一个位置和调用前的 bp 压入栈内,那么只需要先令 sp = bp,然后分别弹出 bppc 的新值即可。
ADJ x sp = sp + x。这部分等价于函数开辟或回收局部变量的空间。需要注意的是,根据题目要求,在开辟空间(x < 0)时,需要将这部分空间全部置零。
LI ax = *ax,也就是根据地址取值。对于局部变量,需要根据 LEA 的结果取值,而对于全局变量只需要根据一个立即数取值。在取值前,地址都需要根据数组参数偏移。
SI 取出栈顶的一个地址 p,然后令 *p = a,也就是将 ax 写入到栈顶的一个地址中。
PSH ax 压入栈中。
EXIT 退出程序。
其余部分 剩下的基本都是系统内置函数的 caller 和运算符处理了。关于运算符处理可以参考前面提到的策略。

由于字节码可能并不包含 EXIT 字节码,我们可以考虑将这个字节码写在栈上,然后模拟 JSR 事件,将指向 EXIT 的指针压入栈中。在上面的设计中,我们还将 PSH 指令写到栈上,这个指令可有可无。

在实现好执行字节码的虚拟机后,剩余的事情就是把代码转换为字节码。我们首先实现一个 Reader

struct Reader {
  char ch;
  Reader(bool) {}
  Reader() { ch = getchar(); }
  char seek() { return ch; }
  void skip() { ch = getchar(); }
  bool eof() { return ch == EOF; }
  void read(char &c) { c = seek(); skip(); }
  char read() { char res; return read(res), res; }
} reader(false);

这个读入类需要在主函数调用 reader = Reader() 激活,这是因为我们无法保证默认的读入操作在打开读写文件之前。

然后是实现 Token 流,也就是每次从读入类读出一个 Token。我们根据输入 seek 到的字符考虑:

  • 如果这个字符是 #,则当前行是头文件引用,直接当作注释处理就好;
  • 如果这个字符是字母,则贪心的读入一个只包含字母、数字和下划线的字符串,这个字符串可能为名称或者保留字,需要通过匹配确定。特别的,如果匹配到的保留字是 using,则一样可以看作注释处理;
  • 如果这个字符是数字,则贪心的读入一个数字即可;
  • 否则,我们认为这个字符是符号的一部分,贪心匹配符号即可。

接下来就是递归处理 Token 流并得到整个字节码架构。这里有一些需要注意的点。

  1. 我们需要在每个代码块中新增一个作用域,形成作用域栈。在定义局部变量时,使用 ADJ 开辟空间,向当前作用域写入大小和偏移量等信息,在离开作用域时则需要撤销更改,同时计算好需要回收的内存,通过 ADJ 指令释放。

  2. 在调用函数时,考虑到函数作用域的条件,我们需要按顺序执行下面的操作:在原先的作用域下计算各个参数并按顺序压入栈中 $\rightarrow$ 将作用域栈移动到另一个栈中,只保留全局变量 $\rightarrow$ 新增作用域,由于栈形态确定,可以轻易得到每个形参对应的基址偏移量 $\rightarrow$ 将形参绑定到作用域上,随后运行函数体 $\rightarrow$ 在函数 LEA 之后,手动 POP 出所有参数,并且将作用域移回。

  3. 本题中的控制中断语句只有 return,而没有 breakcontinue,那么只需要在计算好 return 对应的返回值之后,调用 LEA 离开函数即可。函数的返回值储存在 ax 中。

  4. 需要注意本题中的一些细节,比如变量定义时自动初始化为 0,以及函数默认返回值为 0 等。前者可以在 ADJ 实现,已经提及,而后者可以在函数体的字节码最后,在 LEA 前加入 IMM 0

  5. 在表达式树上,每个表达式的默认返回值都是右值,而对于赋值的情况,左表达式的最后一句应当是 LI(因为在正确的代码下,这个值是一个左值),此时只需要撤销这个 LI 操作,就可以获得原始地址。这个方案同时适用于 cin

  6. cincout 可以看作任意参数,而对于 <<>> 操作符,只需要看作二元运算符,并且保留右操作数的地址或值即可。

  7. 函数只需要映射到 ENT 对应的地址。

  8. 建议在调试期间输出栈信息和寄存器信息,方便找到异常行为。这些行为包括但不限于:在一些位置忘记 POP、回收变量内存错误、基址计算错误等。

#pragma GCC optimize("Ofast")
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <memory>
#include <vector>
#include <string>
using namespace std;

using u8 = unsigned char;
using i16 = short;
using u16 = unsigned short;
using i32 = int;
using u32 = unsigned int;
using i64 = long long;
using u64 = unsigned long long;

struct Reader {
  char ch;
  Reader(bool) {}
  Reader() { ch = getchar(); }
  char seek() { return ch; }
  void skip() { ch = getchar(); }
  bool eof() { return ch == EOF; }
  void read(char &c) { c = seek(); skip(); }
  char read() {  char res; return read(res), res; }
} reader(false);

int input_n, input_a[512], input_cur;

enum TokenType {
  Reserved = 1, Number, Symbol, Name, Eof
};
enum ReservedType {
  Error = 0, Using, Int, If, Else, For, While, Return, Cin, Cout, Endl
};
enum SymbolType {
  ERROR = 0, HASHTAG, LPAREN, RPAREN, LBRACKET, RBRACKET, LCURLY, RCURLY,
  OR, XOR, AND, NOT, EQ, NE, LT, GT, LE, GE, ADD, SUB, MUL, DIV, MOD, NEG, SHL, SHR,
  SEMICOLON, COMMA, DOT, ASSIGN
};
enum InstructionType {
  _LEA, _LEAP, _IMM, _JMP, _JSR, _BZ, _BNZ, _ENT, _ADJ, _LEV, _LI, _SI, _PSH,
  _OR, _XOR, _AND, _NOT, _EQ, _NE, _LT, _GT, _LE, _GE,
  _ADD, _SUB, _MUL, _DIV, _MOD, _NEG, _SHL, _SHR, _CIN, _COUT, _PCHR, _EXIT
};


namespace VM {
const int MEM_SIZE = 1 << 22;
const int BYTE_CODE_LENGTH = 1 << 12;
i64 mem[MEM_SIZE];
i64 bytecode[BYTE_CODE_LENGTH];

int run(i64 start) {
  i64 *pc = (i64*)start, *sp = mem + MEM_SIZE, *bp = sp, ax, tmp;
  // finally execute _PSH and _EXIT by modifying pc
  *--sp = _EXIT;
  *--sp = _PSH;
  tmp = (i64)sp;
  *--sp = tmp;

  while (1) {
    i64 ins = *pc++;
    switch (ins) {
    case _LEA: ax = (i64)(bp + *pc++); break;
    case _LEAP: tmp = ax * (*pc++); ax = (*sp++) + tmp; break;
    case _IMM: ax = *pc++; break;
    case _JMP: pc = (i64*)*pc; break;
    case _JSR: *--sp = (i64)(pc + 1); pc = (i64*)(*pc); break;
    case _BZ: pc = ax ? pc + 1 : (i64*)(*pc); break;
    case _BNZ: pc = ax ? (i64*)(*pc) : pc + 1; break;
    case _ENT: *--sp = (i64)bp; bp = sp; break;
    case _ADJ: if (*pc < 0) memset(sp + *pc, 0, sizeof(i64) * -(*pc)); sp = sp + *pc++; break;
    case _LEV: sp = bp; bp = (i64*)*sp++; pc = (i64*)*sp++; break;
    case _LI: ax = *(i64*)ax; break;
    case _SI: *(i64*)*sp++ = ax; break;
    case _PSH: *--sp = ax; break;
    case _OR: ax = *sp++ || ax; break;
    case _XOR: ax = bool(*sp++) ^ bool(ax); break;
    case _AND: ax = *sp++ && ax; break;
    case _NOT: ax = !ax; break;
    case _EQ: ax = *sp++ == ax; break;
    case _NE: ax = *sp++ != ax; break;
    case _LT: ax = *sp++ < ax; break;
    case _GT: ax = *sp++ > ax; break;
    case _LE: ax = *sp++ <= ax; break;
    case _GE: ax = *sp++ >= ax; break;
    case _ADD: ax = *sp++ + ax; break;
    case _SUB: ax = *sp++ - ax; break;
    case _MUL: ax = *sp++ * ax; break;
    case _DIV: ax = *sp++ / ax; break;
    case _MOD: ax = *sp++ % ax; break;
    case _NEG: ax = -ax; break;
    case _SHL: printf("%d", (int)ax); sp++; break;
    case _SHR: *(i64*)ax = input_a[input_cur++]; sp++; break;
    case _PCHR: putchar(ax); sp++; break;
    case _EXIT: return (int)ax;
    }
  }
}
};

const u32 ID_HASH_LENGTH = 1 << 12;
char* id_hash[ID_HASH_LENGTH];
u32 easy_hash(const char *str) {
  u32 res = 0;
  while (*str != '\0')
    res = res * 331 + *(str ++);
  return res;
}
u32 insert_id(const char *str) {
  u32 hsh = easy_hash(str) & (ID_HASH_LENGTH - 1);
  while (id_hash[hsh] != nullptr && strcmp(id_hash[hsh], str))
    hsh = (hsh + 1) & (ID_HASH_LENGTH - 1);
  if (id_hash[hsh] == nullptr) {
    char* strr = new char[strlen(str) + 1];
    strcpy(strr, str);
    id_hash[hsh] = strr;
  }
  return hsh;
}

namespace TokenFlow {
template <typename T>
struct Match {
  const static int MAX_NODE = 128;
  u8 transition[MAX_NODE][256];
  T info[MAX_NODE];
  u8 cnt;
  Match(const vector<pair<const char*, T>> &vec) {
    cnt = 0;
    for (auto &ele: vec) {
      const char *ptr = ele.first;
      u8 cur = 0;
      while (*ptr != '\0') {
        u8 &nxt = transition[cur][(u8)(*(ptr ++))];
        if (nxt == 0) nxt = ++ cnt;
        cur = nxt;
      }
      info[cur] = ele.second;
    }
  }
  T readFromReader() const {
    u8 cur = 0, temp = 0;
    while ((temp = transition[cur][(u8)reader.seek()]))
      cur = temp, reader.skip();
    return info[cur];
  }
  template <typename T2>
  T readFromStr(T2 str) const {
    u8 cur = 0, temp = 0;
    while (*str != '\0') {
      temp = transition[cur][(u8)(*(str ++))];
      if (temp == 0) return info[0];
      cur = temp;
    }
    return info[cur];
  }
};

const Match<ReservedType> resMatch(vector<pair<const char*, ReservedType>>{
  {"using", Using}, {"int", Int}, {"for", For}, {"while", While}, {"if", If},
  {"else", Else}, {"cin", Cin}, {"cout", Cout}, {"endl", Endl}, {"return", Return}
});
const Match<SymbolType> symMatch(vector<pair<const char*, SymbolType>>{
  {"+", ADD}, {"-", SUB}, {"*", MUL}, {"/", DIV}, {"%", MOD}, {"^", XOR}, {"#", HASHTAG},
  {"==", EQ}, {">", GT}, {"<", LT}, {">=", GE}, {"<=", LE}, {"!=", NE}, {"&&", AND}, {"||", OR}, {"!", NOT},
  {"(", LPAREN}, {")", RPAREN}, {"[", LBRACKET}, {"]", RBRACKET}, {"{", LCURLY}, {"}", RCURLY},
  {";", SEMICOLON}, {",", COMMA}, {".", DOT}, {"=", ASSIGN}, {"<<", SHL}, {">>", SHR}
});

struct Token {
  TokenType type;
  union {
    ReservedType rtype;
    int num;
    SymbolType stype;
    u32 id;
  };
  Token(TokenType type): type(type) {}
  Token(ReservedType rtype): type(Reserved), rtype(rtype) {}
  Token(int num): type(Number), num(num) {}
  Token(SymbolType stype): type(Symbol), stype(stype) {}
  Token(u32 id): type(Name), id(id) {}
  bool operator == (const Token &tk) const {
    if (type != tk.type)
      return false;
    if (type == Reserved)
      return rtype == tk.rtype;
    if (type == Symbol)
      return stype == tk.stype;
    return true;
  }
  bool operator != (const Token &tk) const {
    return !operator==(tk);
  }
};

Token __current(Eof);
void read_next_token() {
  while (isspace(reader.seek()))
    reader.skip();
  if (reader.eof())
    __current = Eof;
  else if (isdigit(reader.seek())) {
    int num = 0;
    while (isdigit(reader.seek()))
      num = num * 10 + (reader.read() - '0');
    __current = num;
  }
  else if (isalpha(reader.seek()) || reader.seek() == '_') {
    string str = "";
    while (isalnum(reader.seek()) || reader.seek() == '_')
      str += reader.read();
    auto tp = resMatch.readFromStr(str.c_str());
    if (tp == Error)
      __current = insert_id(str.c_str());
    else if (tp == Using) {
      while (!reader.eof() && reader.seek() != '\n')  reader.skip();
      read_next_token();
      return;
    }
    else __current = tp;
  }
  else {
    auto tp = symMatch.readFromReader();
    if (tp == HASHTAG) {
      while (!reader.eof() && reader.seek() != '\n')  reader.skip();
      read_next_token();
    }
    else __current = tp;
  }
}
};

const TokenFlow::Token& tk() { return TokenFlow::__current; }
void nxt() { TokenFlow::read_next_token(); }

// declare => sizes of array
int* declare[ID_HASH_LENGTH];
i64 address[ID_HASH_LENGTH];
bool global[ID_HASH_LENGTH];
int SINGLE_VAR[1] = {1};

int* get_array_def_params() {
  vector<int> res;
  while (tk() == LBRACKET) {
    nxt();
    res.push_back(tk().num); 
    nxt(); nxt();
  }
  res.push_back(1);
  for (int i = (int)res.size() - 2; i >= 0; i --)
    res[i] *= res[i + 1];
  int *p = new int[res.size()];
  for (int i = 0; i < (int)res.size(); i ++)
    p[i] = res[i];
  return p;
}

i64 *ptr = VM::bytecode;
int stk_tp = 0;
struct RecoverVar {
  vector<tuple<u32, int, bool, int*>> rec;
  void cover(u32 i, int loc, int* arr = SINGLE_VAR) {
    rec.push_back({i, address[i], global[i], declare[i]});
    address[i] = loc;
    declare[i] = arr;
    global[i] = false;
  }
  void recover(int params = 0) {
    int U = -params;
    for (auto &ele: rec) {
      u32 id = get<0>(ele);
      U += declare[id][0];
      address[id] = get<1>(ele);
      global[id] = get<2>(ele);
      declare[id] = get<3>(ele);
    }
    if (U != 0)
      *ptr++ = _ADJ, *ptr++ = U, stk_tp += U;
  }
};

int precedence(SymbolType s) {
  switch (s) {
  case SHL: case SHR: return 1;
  case ASSIGN: return 2;
  case OR: return 3;
  case AND: return 4;
  case XOR: return 5;
  case EQ: case NE: return 6;
  case LE: case GE: case LT: case GT: return 7;
  case ADD: case SUB: return 8;
  case MUL: case DIV: case MOD: return 9;
  case NEG: case NOT: return 10;
  default: return 0;
  }
}

void get_expr();
bool nxt_endl = false;
u32 syscall_putchar;
void get_unit() {
  if (tk() == LPAREN)
    nxt(), get_expr(), nxt();
  else if (tk().type == Name) {
    u32 name = tk().id;
    nxt();
    if (tk() == LPAREN) {
      nxt();
      int num = 0;
      while (tk() != RPAREN) {
        if (num) nxt();
        ++ num; get_expr(); *ptr++ = _PSH;
      }
      if (name == syscall_putchar)
        *ptr++ = _PCHR;
      else {
        *ptr++ = _JSR; *ptr++ = address[name];
        if (num) *ptr++ = _ADJ, *ptr++ = num;
      }
      nxt();
    }
    else {
      if (global[name])
        *ptr++ = _IMM, *ptr++ = (i64)(VM::mem) + address[name] * sizeof(i64);
      else
        *ptr++ = _LEA, *ptr++ = address[name];
      int num = 0;
      while (tk() == LBRACKET) {
        *ptr++ = _PSH;
        nxt(); get_expr(); nxt();
        *ptr++ = _LEAP; *ptr++ = declare[name][++ num] * sizeof(i64);
      }
      *ptr++ = _LI;
    }
  }
  else if (tk().type == Number)
    *ptr++ = _IMM, *ptr++ = tk().num, nxt();
  else if (tk() == Endl)
    nxt_endl = true, *ptr++ = _IMM, *ptr++ = 10, nxt();
  else
    nxt(); // cin & cout
}

void get_expr() {
  vector<SymbolType> stk;
  int state = 0, temp = 0, temp2 = 0;
  if (tk().type == Name) {
    get_unit();
    if (tk() == ASSIGN) {
      // discard LI and cover
      *(ptr - 1) = _PSH; nxt();
      get_expr(); *ptr++ = _SI;
      return;
    }
    state = 1;
  }

  #define pop() {\
    if (nxt_endl)\
      *ptr++ = _PCHR, nxt_endl = false;\
    else if (stk.back() == SHR)\
      *(ptr - 1) = _SHR;\
    else\
      *ptr++ = stk.back() - OR + _OR;\
    stk.pop_back();\
  }
  while (1) {
    if (state == 0) {
      if (tk() == NOT) stk.push_back(NOT), nxt();
      else if (tk() == SUB) stk.push_back(NEG), nxt();
      else if (tk() == ADD) nxt();
      else get_unit(), state = 1;
    }
    else {
      if (tk().type == Symbol && (temp = precedence(tk().stype))) {
        while (!stk.empty() && (temp2 = precedence(stk.back())) >= temp)
          pop();
        stk.push_back(tk().stype); nxt();
        *ptr++ = _PSH; state = 0;
      }
      else break;
    }
  }
  while (!stk.empty())
    pop();
}

RecoverVar rv;

void get_stmt();

void get_stmts() {
  RecoverVar nrv;
  swap(rv, nrv);
  while (tk() != RCURLY)
    get_stmt();
  swap(rv, nrv);
  nrv.recover();
}

void get_basic_stmt() {
  if (tk() == Int) {
    int o = 0;
    do {
      nxt(); u32 name = tk().id; nxt();
      int *v = get_array_def_params();
      o -= v[0]; stk_tp -= v[0];
      rv.cover(name, stk_tp, v);
    } while (tk() == COMMA);
    *ptr++ = _ADJ; *ptr++ = o;
  }
  else get_expr();
}

void get_stmt() {
  if (tk() == SEMICOLON)
    nxt();
  else if (tk() == LCURLY)
    nxt(), get_stmts(), nxt();
  else if (tk() == If) {
    nxt(); nxt(); get_expr(); nxt();
    *ptr++ = _BZ;
    i64 *step_ptr = ptr++;
    get_stmt();
    if (tk() == Else) {
      *ptr++ = _JMP;
      i64 *end_ptr = ptr++;
      *step_ptr = (i64)ptr;
      nxt(); get_stmt();
      *end_ptr = (i64)ptr;
    }
    else
      *step_ptr = (i64)ptr;
  }
  else if (tk() == For) {
    RecoverVar nrv;
    swap(rv, nrv);
    nxt(); // for
    nxt(); // (
    if (tk() != SEMICOLON) get_basic_stmt(); nxt();
    i64 start_ptr = (i64)ptr;
    if (tk() == SEMICOLON) *ptr++ = _IMM, *ptr++ = 1;
    else get_expr();
    *ptr++ = _BNZ;
    i64 *stmt_ptr = ptr++;
    *ptr++ = _JMP;
    i64 *end_ptr = ptr++;
    nxt(); // ;
    i64 effect_ptr = (i64)ptr;
    if (tk() != RPAREN) get_basic_stmt();
    nxt();
    *ptr++ = _JMP; *ptr++ = start_ptr;
    *stmt_ptr = (i64)ptr;
    get_stmt();
    *ptr++ = _JMP; *ptr++ = effect_ptr;
    *end_ptr = (i64)ptr;
    swap(rv, nrv);
    nrv.recover();
  }
  else if (tk() == While) {
    nxt(); nxt();
    i64 start_ptr = (i64)ptr;
    get_expr(); nxt();
    *ptr++ = _BZ;
    i64 *end_ptr = ptr ++;
    get_stmt();
    *ptr++ = _JMP; *ptr++ = start_ptr;
    *end_ptr = (i64)ptr;
  }
  else if (tk() == Return) {
    nxt(); get_expr(); nxt();
    *ptr++ = _LEV;
  }
  else {
    get_basic_stmt();
    nxt();
  }
}

void get_func(u32 name) {
  address[name] = (i64)ptr;
  global[name] = true;
  *ptr++ = _ENT;
  nxt();
  RecoverVar nrv;
  vector<u32> params;
  while (tk() != RPAREN) {
    if (!params.empty()) nxt();
    nxt(); params.push_back(tk().id); nxt();
  }
  nxt();
  int n = params.size();
  for (int i = 0; i < n; i ++)
    nrv.cover(params[i], n + 1 - i);
  swap(rv, nrv);
  get_stmt();
  *ptr++ = _IMM;
  *ptr++ = 0;
  *ptr++ = _LEV;
  swap(rv, nrv);
  nrv.recover(n);
}

void get_func_and_var() {
  int bss = 0;
  while (tk() != Eof) {
    nxt(); // int
    u32 name = tk().id;
    nxt(); // name
    if (tk() == LPAREN)
      get_func(name);
    else {
      // var
      declare[name] = get_array_def_params();
      address[name] = bss;
      bss += declare[name][0];
      global[name] = true;
      while (tk() == COMMA) { 
        nxt(); name = tk().id; nxt();
        declare[name] = get_array_def_params();
        address[name] = bss;
        bss += declare[name][0];
        global[name] = true;
      }
      nxt();
    }
  }
}

void rint(int &x) {
  while (!isdigit(reader.seek()))
    reader.skip();
  while(isdigit(reader.seek()))
    x = x * 10 + (reader.read() ^ 48);
}

int main() {
  reader = Reader();
  rint(input_n);
  for (int i = 0; i < input_n; i ++)
    rint(input_a[i]);
  syscall_putchar = insert_id("putchar");
  u32 syscall_main = insert_id("main");
  nxt(); get_func_and_var();
  VM::run(address[syscall_main]);
  return 0;
}
tiger2005 Avatar