JM Tech Power

jm-tech-power-official/JMLang

A programming language made by JM Tech Power

Public Uncategorized
Clone jm-tech-power-official/jmlang.jmproj
interp.cpp
#include <iostream>
#include <fstream>
#include <map>
#include <vector>
#include <stack>
#include <set>
#include <ctime>
#include <chrono>
#include <curl/curl.h>
#include <string>
#include <sstream>
#define INF 0x3f3f3f3f
using namespace std;
// download lib online
size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream) {
    size_t written = fwrite(ptr, size, nmemb, stream);
    return written;
}
string unescape(const string& s) {
    string r;
    for (size_t i = 0; i < s.size(); ) {
        if (s[i] == '&') {
            size_t semi = s.find(';', i);
            if (semi != string::npos && semi - i <= 10) {
                string ent = s.substr(i + 1, semi - i - 1);   // 去掉 & 和 ;
                int code = -1;
                if (ent == "lt") code = '<';
                else if (ent == "gt") code = '>';
                else if (ent == "amp") code = '&';
                else if (ent == "quot") code = '"';
                else if (ent == "apos") code = '\'';
                else if (ent == "nbsp") code = ' ';
                else if (ent.size() > 1 && ent[0] == '#') {
                    try {
                        if (ent[1] == 'x' || ent[1] == 'X')
                            code = stoi(ent.substr(2), nullptr, 16);  // &#x22;
                        else
                            code = stoi(ent.substr(1));               // &#34;
                    } catch (...) { code = -1; }
                }
                if (code >= 0 && code < 128) {   // ASCII 直接塞进去
                    r += char(code);
                    i = semi + 1;
                    continue;
                }
            }
        }
        r += s[i++];
    }
    return r;
}
int download_lib_on_web(string lib) {
    CURL *curl;
    FILE *fp;
    CURLcode res;
    string surl = "https://www.jmtechpower.com/jm-tech-power-official/jmlang?tab=source&ref=refs/heads/main&path="+lib;
    const char *url = surl.c_str();
    const char *outfilename = lib.c_str();

    curl = curl_easy_init();
    if (curl) {
        fp = fopen(outfilename, "wb");
        if (!fp) {
            cerr << "Failed to open or create output file." << endl;
            return 1;
        }

        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);

        res = curl_easy_perform(curl);

        curl_easy_cleanup(curl);
        fclose(fp);
        if (res != CURLE_OK) {
            cerr << "Failed to download " << outfilename << std::endl;
            remove(outfilename);
            exit(1);
        }
    }
    ifstream fin(lib);
    stringstream ss;
    ss << fin.rdbuf();
    string text = ss.str();

    const string L = "<pre class=\"jr-blob-code\">";
    const string R = "</pre>";

    size_t pos = 0;
    bool runed = 0;
    while ((pos = text.find(L, pos)) != string::npos) {
        runed = 1;
        size_t start = pos + L.size();
        size_t end = text.find(R, start);
        if (end == string::npos) break;
        string y = unescape(text.substr(start, end - start));
        if (!y.empty() && y[0] == '\n') y.erase(0, 1);
        ofstream fout(outfilename);
        fout.clear();
        fout << y << endl;
        pos = end + R.size();
    }
    if (!runed) {
        cerr << "No library named " << lib << " found." << endl;
        remove(outfilename);
        exit(1);
    }
    return 0;
}
///////////////////////////////////////////////
vector<string> loaded;
// Lexer
vector<string> tokenizeRaw(string str) {
    vector<string> tokens;
    string token;
    bool inStr = false;
    for (int i = 0; i < str.size(); i++) {
        if (inStr && str[i] == '\\' && i + 1 < str.size()) {
            token += str[i];
            token += str[i + 1];
            i++;
        } else if (str[i] == '"') {
            inStr = !inStr;
            token += str[i];
        } else if (str[i] == ' ' && !inStr) {
            if (token == "") continue;
            tokens.push_back(token);
            token = "";
        } else token += str[i];
    }
    if (token != "") tokens.push_back(token);
    return tokens;
}
// Parser
string unescapeStr(const string& s) {
    string r;
    for (int i = 0; i < s.size(); i++) {
        if (s[i] == '\\' && i + 1 < s.size()) {
            i++;
            if (s[i] == 'n') r += '\n';
            else if (s[i] == 't') r += '\t';
            else r += s[i];
        } else {
            r += s[i];
        }
    }
    return r;
}
enum Operate {
    set,
    out,
    tick,
    times,
    in,
    lt,
    le,
    gt,
    ge,
    eq,
    ne,
    add,
    minus,
    multiply,
    divide,
    mod,
    var,
    land,
    lor,
    lnot,
    slen,
    sat,
    ord,
    chr,
    fileread,
    filelines,
    fileline,
    fileexists,
};
struct Node {
    Operate op;
    string var;
    Node *left, *right;
};
struct Value {
    bool isStr = false;
    string s = "";
    int obj = 0;
    int ptr = 0;
    int n = 0;
};
struct PtrInfo {
    int obj = 0;
    int depth = 0;
    int fid = 0;
    string arr;
    bool isElem = false;
    int idx = 0;
};
map<string, int> priority;
std::set<string> unaryOps = {"not","len","ord","chr","read","lines","exists"};
vector<map<string,Value>> frames;
map<string, int> labels;
stack<int> st;
map<int, int> labels2;
map<int, int> elseOf;
map<int, int> elseIf;
map<string, int> funcs;
stack<int> callStack;
vector<int> loopSt;
map<int, int> loopOf;
map<int, map<string, Value>> heap;
int objCount = 0;
map<string, vector<string>> structs;
std::set<string> classes;
map<string, string> parentOf;
vector<PtrInfo> ptrs(1);
int frameSerial = 0;

vector<string> srcFile;   // 每一行来自哪个文件
vector<int> srcLine;      // 是那个文件的第几行
int curLine = -1;         // 现在正在跑 loaded 里的第几行
void die(string msg, int at = -1) {
    if (at == -1) at = curLine;
    if (at >= 0 && at < (int)loaded.size()) {
        cerr << "[" << srcFile[at] << " Line " << srcLine[at] << "] " << msg << endl;
        cerr << "    > " << loaded[at] << endl;
    } else {
        cerr << msg << endl;
    }
    exit(1);
}

void init() {
    priority["set"] = 0;
    priority["out"] = 0;
    priority["or"]  = 1;
    priority["and"] = 2;
    priority["not"] = 3;
    priority["<"]  = 4;  priority["<="] = 4;
    priority[">"]  = 4;  priority[">="] = 4;
    priority["=="] = 4;  priority["!="] = 4;
    priority["+"]  = 5;  priority["-"]  = 5;
    priority["*"]  = 6;  priority["/"]  = 6;
    priority["%"]  = 6;
    priority["len"] = 7;  priority["ord"] = 7;  priority["chr"] = 7;
    priority["at"]  = 8;
    priority["read"] = 7;  priority["lines"] = 7;  priority["exists"] = 7;
    priority["line"] = 8;
}
map<string,Value>& scope(const string& name) {
    if (name[0] == '@') return frames[0];
    return frames.back();
}
Node* parseRaw(vector<string> tokens) {
    if (tokens.empty()) return nullptr;
    Node *root = new Node();
    int ti = -1;
    int mn = INF;
    for (int i = 0; i < tokens.size(); i++) {
        auto it = priority.find(tokens[i]);
        if (it != priority.end() && it->second <= mn) {
            mn = it->second;
            ti = i;
        }
    }
    if (ti != -1 && unaryOps.count(tokens[ti])) {
        for (int i = 0; i < tokens.size(); i++) {
            auto it = priority.find(tokens[i]);
            if (it != priority.end() && it->second == mn) { ti = i; break; }
        }
    }
    if (ti == -1) {
        if (tokens[0] == "-$in$-") {
            root->op = Operate::in;
            return root;
        } else if (tokens[0] == "-$time$-") {
            root->op = Operate::times;
            return root;
        } else if (tokens[0] == "-$tick$-") {
            root->op = Operate::tick;
            return root;
        }
        if (tokens.size() > 1) die("Can't understand: " + tokens[1]);
        root->op = Operate::var;
        root->var = tokens[0];
        if (root->var[0] == '"')
            root->var = "\"" + unescapeStr(root->var.substr(1, root->var.size() - 2)) + "\"";
        return root;
    }
    if (tokens[ti] == "set") {
        root->op = Operate::set;
    } else if (tokens[ti] == "out") {
        root->op = Operate::out;
    } else if (tokens[ti] == "+") {
        root->op = Operate::add;
    } else if (tokens[ti] == "-") {
        root->op = Operate::minus;
    } else if (tokens[ti] == "*") {
        root->op = Operate::multiply;
    } else if (tokens[ti] == "/") {
        root->op = Operate::divide;
    } else if (tokens[ti] == "<") {
        root->op = Operate::lt;
    } else if (tokens[ti] == "<=") {
        root->op = Operate::le;
    } else if (tokens[ti] == ">=") {
        root->op = Operate::ge;
    } else if (tokens[ti] == "==") {
        root->op = Operate::eq;
    } else if (tokens[ti] == "!=") {
        root->op = Operate::ne;
    } else if (tokens[ti] == ">") {
        root->op = Operate::gt;
    } else if (tokens[ti] == "%") {
        root->op = Operate::mod;
    } else if (tokens[ti] == "and") {
        root->op = Operate::land;
    } else if (tokens[ti] == "or") {
        root->op = Operate::lor;
    } else if (tokens[ti] == "not") {
        root->op = Operate::lnot;
    } else if (tokens[ti] == "len") {
        root->op = Operate::slen;
    } else if (tokens[ti] == "at") {
        root->op = Operate::sat;
    } else if (tokens[ti] == "ord") {
        root->op = Operate::ord;
    } else if (tokens[ti] == "chr") {
        root->op = Operate::chr;
    } else if (tokens[ti] == "read") {
        root->op = Operate::fileread;
    } else if (tokens[ti] == "lines") {
        root->op = Operate::filelines;
    } else if (tokens[ti] == "line") {
        root->op = Operate::fileline;
    } else if (tokens[ti] == "exists") {
        root->op = Operate::fileexists;
    }
    vector<string> a;
    vector<string> b;
    for (int i = 0;i<tokens.size();i++) {
        string token = tokens[i];
        if (i < ti) {
            a.push_back(token);
        } else if (i > ti) {
            b.push_back(token);
        }
    }
    root->left = parseRaw(a);
    root->right = parseRaw(b);
    return root;
}
// 缓存:同样的一行只拆一次、同样的表达式只建一次树
map<string, vector<string>> tokCache;
const vector<string>& tokenize(const string& str) {
    auto it = tokCache.find(str);
    if (it != tokCache.end()) return it->second;
    return tokCache[str] = tokenizeRaw(str);
}
map<string, Node*> parseCache;
Node* parse(const vector<string>& tokens) {
    string key;
    for (auto &t : tokens) { key += t; key += '\x01'; }
    auto it = parseCache.find(key);
    if (it != parseCache.end()) return it->second;
    return parseCache[key] = parseRaw(tokens);
}
// Evaluator
int s2i(const string& s) {
    if (s[0] == '-') return -s2i(s.substr(1));
    int multi = 1;
    int ans = 0;
    for (int i = s.size()-1; i>=0; i--) {
        ans+=multi*(s[i]-'0');
        multi*=10;
    }
    return ans;
}
string i2s(int x) {
    if (x == 0) return "0";
    if (x < 0) return "-"+i2s(-x);
    string ans = "";
    while (x) {
        ans = char(x%10+'0') + ans;
        x /= 10;
    }
    return ans;
}
bool isNumber(const string& s) {
    if (s.empty() || s == "-") return false;
    for (int i = 0; i<s.size(); i++) {
        if (i == 0 && s[i] == '-') continue;
        if (s[i] < '0' || s[i] > '9') return false;

    }
    return true;
}
Value num(int x)    { Value v; v.n = x; return v; }
// 拿到"文字形式":数字现场转成字符串,其他的直接用 s
string txt(const Value &v) {
    if (!v.isStr && !v.obj && !v.ptr) return i2s(v.n);
    return v.s;
}
// 拿到"数字形式"
int toInt(const Value &v) {
    if (v.isStr) return s2i(v.s);
    return v.n;
}
Value str(string x) { return {true, x}; }
bool truthy(Value v) {
    if (v.obj || v.ptr) return true;
    if (v.isStr) return !v.s.empty();
    return v.n != 0;
}
void doReturn(Value val, int &next) {
    frames.pop_back();
    frames.back()["ret"] = val;
    next = callStack.top();
    callStack.pop();
}
Value elevate(Node *root);
string resolveName(const string& raw) {
    int p = raw.find('<');
    if (p == -1) return raw;
    string arr = raw.substr(0, p);
    string idx = raw.substr(p+1,raw.size()-p-2);
    idx = txt(elevate(parse(tokenize(idx))));
    return arr + "<" + idx + ">";
}
// "p.pos.x" → ["p","pos","x"](<> 里面的点不拆)
vector<string> splitDots(const string& s) {
    vector<string> parts;
    string cur;
    int depth = 0;
    for (char c : s) {
        if (c == '<') depth++;
        if (c == '>') depth--;
        if (c == '.' && depth == 0) { parts.push_back(cur); cur = ""; }
        else cur += c;
    }
    parts.push_back(cur);
    return parts;
}
// raw 是 "对象.字段" 就返回那个字段的位置,不是就返回 nullptr
Value* fieldRef(const string& raw, bool create) {
    vector<string> parts = splitDots(raw);
    if (parts.size() < 2) return nullptr;
    string base = resolveName(parts[0]);
    auto &sc = scope(parts[0]);
    if (!sc.count(base) || sc[base].obj == 0) return nullptr;  // 不是对象,当普通名字
    Value *v = &sc[base];
    for (int i = 1; i < parts.size(); i++) {
        if (v->obj == 0) die("Not an object: " + parts[i - 1]);
        auto &fields = heap[v->obj];
        string f = resolveName(parts[i]);
        if (!create && !fields.count(f)) die("No field: " + f);
        v = &fields[f];
    }
    return v;
}
string ptrKey(PtrInfo &pi) {
    return pi.isElem ? pi.arr + "<" + i2s(pi.idx) + ">" : pi.arr;
}
Value newPtr(PtrInfo pi) {
    ptrs.push_back(pi);
    Value v;
    v.ptr = ptrs.size() - 1;
    v.s = "<ptr " + ptrKey(pi) + ">";
    return v;
}
Value makePtr(string raw) {
    PtrInfo pi;
    string key;
    vector<string> parts = splitDots(raw);
    if (parts.size() >= 2) {                       // 可能是对象字段
        string base = resolveName(parts[0]);
        auto &sc = scope(parts[0]);
        if (sc.count(base) && sc[base].obj) {
            Value *v = &sc[base];
            for (int i = 1; i + 1 < parts.size(); i++) {
                if (!v->obj) die("Not an object: " + parts[i - 1]);
                v = &heap[v->obj][resolveName(parts[i])];
            }
            if (!v->obj) die("Not an object: " + parts[parts.size() - 2]);
            pi.obj = v->obj;
            key = resolveName(parts.back());
        }
    }
    if (!pi.obj) {                                 // 普通变量
        key = resolveName(raw);
        pi.depth = (raw[0] == '@') ? 0 : frames.size() - 1;
        pi.fid = frames[pi.depth]["__fid"].n;
    }
    int p = key.find('<');
    if (p != -1 && isNumber(key.substr(p + 1, key.size() - p - 2))) {
        pi.isElem = true;
        pi.arr = key.substr(0, p);
        pi.idx = s2i(key.substr(p + 1, key.size() - p - 2));
    } else {
        pi.arr = key;
    }
    return newPtr(pi);
}
// 指针指向的那个"家"(某个栈帧或某个对象)
map<string, Value>& ptrHome(PtrInfo &pi) {
    if (pi.obj) return heap[pi.obj];
    if (pi.depth >= frames.size() || frames[pi.depth]["__fid"].n != pi.fid)
        die("Dangling pointer: " + pi.arr + " is gone (its function already returned)");
    return frames[pi.depth];
}
Value& derefRef(Value p) {                         // set *p ...
    if (!p.ptr) die("Not a pointer: " + txt(p));
    PtrInfo &pi = ptrs[p.ptr];
    return ptrHome(pi)[ptrKey(pi)];
}
Value derefGet(Value p) {                          // $*p
    if (!p.ptr) die("Not a pointer: " + txt(p));
    PtrInfo &pi = ptrs[p.ptr];
    auto &home = ptrHome(pi);
    string k = ptrKey(pi);
    if (!home.count(k)) {
        if (pi.isElem) return num(0);
        die("Pointer points to unset variable: " + k);
    }
    return home[k];
}
bool samePtr(PtrInfo &a, PtrInfo &b) {
    return a.obj == b.obj && a.depth == b.depth && a.fid == b.fid && ptrKey(a) == ptrKey(b);
}
// 读变量($x / $a<1> / $p.x)
Value getVar(const string& raw) {
    if (raw.find_first_of(".<*") == string::npos) {
        auto &sc = scope(raw);
        auto it = sc.find(raw);
        if (it == sc.end()) die("Variable not set: $" + raw);
        return it->second;
    }
    if (raw[0] == '*') return derefGet(getVar(raw.substr(1)));
    Value *f = fieldRef(raw, false);
    if (f) return *f;
    string full = resolveName(raw);
    auto &sc = scope(raw);
    if (!sc.count(full)) {
        if (full.find('<') != string::npos) return num(0);
        die("Variable not set: $" + full);
    }
    return sc[full];
}
// 写变量的位置(set x / set a<1> / set p.x)
Value& setRef(const string& raw) {
    if (raw.find_first_of(".<*") == string::npos) return scope(raw)[raw];
    if (raw[0] == '*') return derefRef(getVar(raw.substr(1)));
    Value *f = fieldRef(raw, true);
    if (f) return *f;
    return scope(raw)[resolveName(raw)];
}
Value makeObj(string cls) {
    int id = ++objCount;
    heap[id]["__class"] = str(cls);
    Value v;
    v.obj = id;
    v.s = "<" + cls + "#" + i2s(id) + ">";
    return v;
}
vector<Value> evalArgs(vector<string> &tokens, int from) {
    vector<Value> args;
    vector<string> cur;
    for (int i = from; i < tokens.size(); i++) {
        if (tokens[i] == ",") { args.push_back(elevate(parse(cur))); cur.clear(); }
        else cur.push_back(tokens[i]);
    }
    if (!cur.empty()) args.push_back(elevate(parse(cur)));
    return args;
}
string findMethod(string cls, string m) {
    while (cls != "") {
        if (funcs.count(cls + "." + m)) return cls + "." + m;
        cls = parentOf.count(cls) ? parentOf[cls] : "";
    }
    return "";
}
void enterFunc(string fname, vector<Value> args, Value self, int &next) {
    frames.push_back({});
    frames.back()["__fid"] = num(++frameSerial);
    if (self.obj) frames.back()["self"] = self;
    vector<string> header = tokenize(loaded[funcs[fname]]);
    for (int i = 0; i < args.size() && i + 2 < header.size(); i++) {
        frames.back()[header[i + 2]] = args[i];
    }
    callStack.push(next);
    next = funcs[fname] + 1;
}
long long nowMs() {
    using namespace chrono;
    return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
}
long long startMs = nowMs();
Value elevate(Node *root) {
    if (root == nullptr) die("Code not complete");
    if (root->op == Operate::var) {
        string v = root->var;
        if (v[0] == '"') return str(v.substr(1, v.size() - 2));   // "abc" → 字符串
        if (v[0] == '$') {
            return getVar(v.substr(1));
        }
        if (v[0] == '&') return makePtr(v.substr(1));
        if (isNumber(v)) return num(s2i(v));                       // 123 → 数字
        return str(v);                                            // 其他裸词(比如标签名)
    } else if (root->op == Operate::out) {
        Value v = elevate(root->right);
        cout << txt(v);
        return v;
    } else if (root->op == Operate::in) {
        string w;
        cin >> w;
        return isNumber(w) ? num(s2i(w)) : str(w);
    } else if (root->op == Operate::times) {
        return num((int)time(0));
    } else if (root->op == Operate::tick) {
        return num((int)(nowMs() - startMs));
    } else if (root->op == Operate::lnot) {
        return num(!truthy(elevate(root->right)));
    } else if (root->op == Operate::land) {
        if (!truthy(elevate(root->left))) return num(0);
        return num(truthy(elevate(root->right)));
    } else if (root->op == Operate::lor) {
        if (truthy(elevate(root->left))) return num(1);
        return num(truthy(elevate(root->right)));
    } else if (root->op == Operate::slen) {
        return num(txt(elevate(root->right)).size());               // len "abc" → 3
    } else if (root->op == Operate::ord) {
        string c = txt(elevate(root->right));
        return num(c.empty() ? 0 : (unsigned char)c[0]);         // ord "A" → 65
    } else if (root->op == Operate::chr) {
        return str(string(1, (char)toInt(elevate(root->right)))); // chr 65 → "A"
    } else if (root->op == Operate::sat) {
        string s = txt(elevate(root->left));
        int i = toInt(elevate(root->right));
        if (i < 0 || i >= (int)s.size()) return str("");       // 越界给空字符串
        return str(string(1, s[i]));                             // "abc" at 1 → "b"
    } else if (root->op == Operate::fileexists) {
        ifstream f(txt(elevate(root->right)));
        return num(f ? 1 : 0);
    } else if (root->op == Operate::fileread) {
        ifstream f(txt(elevate(root->right)));
        stringstream ss;
        ss << f.rdbuf();
        return str(ss.str());
    } else if (root->op == Operate::filelines) {
        ifstream f(txt(elevate(root->right)));
        string l;
        int n = 0;
        while (getline(f, l)) n++;
        return num(n);
    } else if (root->op == Operate::fileline) {
        ifstream f(txt(elevate(root->left)));
        int want = toInt(elevate(root->right));
        string l;
        for (int i = 0; getline(f, l); i++) {
            if (i == want) return isNumber(l) ? num(s2i(l)) : str(l);   // 像数字就当数字
        }
        return str("");
    }
    Value A = elevate(root->left);
    Value B = elevate(root->right);

    if (A.ptr || B.ptr) {
        // 指针 + 整数:往后挪几个数组元素
        if (A.ptr && !B.ptr && !B.isStr && !B.obj &&
            (root->op == Operate::add || root->op == Operate::minus)) {
            PtrInfo pi = ptrs[A.ptr];
            if (!pi.isElem) die("Pointer math only works on array elements");
            pi.idx += (root->op == Operate::add ? 1 : -1) * toInt(B);
            return newPtr(pi);
            }
        if (root->op == Operate::eq || root->op == Operate::ne) {
            bool same = A.ptr && B.ptr && samePtr(ptrs[A.ptr], ptrs[B.ptr]);
            return num(root->op == Operate::eq ? same : !same);
        }
        if (root->op == Operate::add && (A.isStr || B.isStr)) return str(txt(A) + txt(B));
        die("Pointer can't do this math");
    }

    if (A.obj || B.obj) {
        if (root->op == Operate::eq) return num(txt(A) == txt(B));
        if (root->op == Operate::ne) return num(txt(A) != txt(B));
        if (root->op == Operate::add && (A.isStr || B.isStr)) return str(txt(A) + txt(B));
        die("Object can't do math");
    }

    // 只要有一边是字符串,+ 就是拼接,== / != 就是比较文字
    if (A.isStr || B.isStr) {
        if (root->op == Operate::add) return str(txt(A) + txt(B));
        if (root->op == Operate::eq)  return num(txt(A) == txt(B));
        if (root->op == Operate::ne)  return num(txt(A) != txt(B));
    }


    if (A.isStr || B.isStr) die("String \"" + (A.isStr ? A.s : B.s) + "\" can't do math");
    int a = A.n;
    int b = B.n;
    if ((root->op == Operate::divide || root->op == Operate::mod) && b == 0) die("Divide by 0");

    if (root->op == Operate::add) {
        return num(a+b);
    } else if (root->op == Operate::minus) {
        return num(a-b);
    } else if (root->op == Operate::multiply) {
        return num(a*b);
    } else if (root->op == Operate::divide) {
        return num(a/b);
    } else if (root->op == Operate::lt) {
        return num(a < b);
    } else if (root->op == Operate::le) {
        return num(a <= b);
    } else if (root->op == Operate::gt) {
        return num(a > b);
    } else if (root->op == Operate::ge) {
        return num(a >= b);
    } else if (root->op == Operate::eq) {
        return num(a == b);
    } else if (root->op == Operate::ne) {
        return num(a != b);
    } else if (root->op == Operate::mod) {
        return num(a % b);
    }
    return num(0);
}
void run(const string& line, int &next) {
    int cur = next-1;
    curLine = cur;
    if (line[0] == ':') return;
    vector<string> tokens = tokenize(line);
    if (tokens.empty()) return;
    if (tokens[0][0] == '#') return;

    if (tokens[0] == "set") {
        if (tokens.size() < 3) die("usage: set variable value");
        string name = tokens[1];
        vector<string> rest(tokens.begin() + 2, tokens.end());
        setRef(name) = elevate(parse(rest));
        return;
    } else if (tokens[0] == "jump") {
        vector<string> rest(tokens.begin() + 1, tokens.end());
        string t = txt(elevate(parse(rest)));
        if (labels.find(t) == labels.end()) {
            if (!isNumber(t)) die("Label not found: " + t);
            next = s2i(t) - 1;
        } else {
            next = labels[t];
        }
        return;
    } else if (tokens[0] == "jumpif") {
        vector<string> cond(tokens.begin() + 1, tokens.end() - 1);
        if (toInt(elevate(parse(cond))) == 0) return;
        string t = tokens.back();
        if (labels.find(t) == labels.end()) {
            if (!isNumber(t)) die("Label not found: " + t);
            next = s2i(t) - 1;
        } else {
            next = labels[t];
        }
        return;
    } else if (tokens[0] == "while") {
        vector<string> cond(tokens.begin()+1,tokens.end());
        if (toInt(elevate(parse(cond))) == 0) {
            next = labels2[cur] + 1;
        }
        return;
    } else if (tokens[0] == "if") {
        vector<string> cond(tokens.begin()+1,tokens.end());
        if (toInt(elevate(parse(cond))) == 0) {
            if (elseOf.count(cur)) next = elseOf[cur] + 1;
            else next = labels2[cur] + 1;
        }
        return;
    } else if (tokens[0] == "else") {
        next = labels2[elseIf[cur]] + 1;
        return;
    } else if (tokens[0] == "break") {
        next = labels2[loopOf[cur]] + 1;
        return;
    } else if (tokens[0] == "continue") {
        next = loopOf[cur];
        return;
    } else if (tokens[0] == "struct") {
        return;
    } else if (tokens[0] == "class") {
        next = labels2[cur] + 1;                    // 跳过整个类
        return;
    } else if (tokens[0] == "new") {
        if (tokens.size() < 3) die("usage: new variable Type args");
        string target = tokens[1], cls = tokens[2];
        vector<Value> args = evalArgs(tokens, 3);
        if (structs.count(cls)) {
            Value o = makeObj(cls);
            vector<string> &fs = structs[cls];
            for (int i = 0; i < fs.size(); i++)
                heap[o.obj][fs[i]] = i < args.size() ? args[i] : num(0);
            setRef(target) = o;
            return;
        }
        if (!classes.count(cls)) die("Type not found: " + cls);
        Value o = makeObj(cls);
        setRef(target) = o;
        string init = findMethod(cls, "init");      // 有 init 就自动调用
        if (init != "") enterFunc(init, args, o, next);
        return;
    } else if (tokens[0] == "func") {
        next = labels2[cur] + 1;
        return;
    } else if (tokens[0] == "end") {
        string opener = tokenize(loaded[labels2[cur]])[0];
        if (opener == "if") return;
        if (opener == "func") {
            doReturn(num(0),next);
            return;
        }
        next = labels2[cur];
        return;
    } else if (tokens[0] == "call") {
        string name = tokens[1];
        vector<Value> argVals = evalArgs(tokens, 2);
        if (name[0] == '$') {                       // 方法调用:call $p.move 1 , 2
            vector<string> parts = splitDots(name.substr(1));
            if (parts.size() < 2) die("usage: call $object.method args");
            string m = parts.back();
            parts.pop_back();
            string objName = parts[0];
            for (int i = 1; i < parts.size(); i++) objName += "." + parts[i];
            Value self = getVar(objName);
            if (!self.obj) die("Not an object: " + objName);
            string cls = heap[self.obj]["__class"].s;
            string fname = findMethod(cls, m);
            if (fname == "") die(cls + " has no method: " + m);
            enterFunc(fname, argVals, self, next);
            return;
        }
        if (!funcs.count(name)) die("Function not found: " + name);
        enterFunc(name, argVals, Value(), next);
        return;
    } else if (tokens[0] == "ret") {
        vector<string> retExpr(tokens.begin() + 1, tokens.end());
        Value val = num(0);
        if (!retExpr.empty()) val = elevate(parse(retExpr));
        doReturn(val, next);
        return;
    } else if (tokens[0] == "write" || tokens[0] == "append") {
        vector<string> pathT, valT;
        bool afterComma = false;
        for (int i = 1; i < tokens.size(); i++) {
            if (!afterComma && tokens[i] == ",") { afterComma = true; continue; }
            if (afterComma) valT.push_back(tokens[i]);
            else pathT.push_back(tokens[i]);
        }
        string path = txt(elevate(parse(pathT)));
        string val = valT.empty() ? "" : txt(elevate(parse(valT)));
        ofstream f;
        if (tokens[0] == "write") f.open(path);
        else f.open(path, ios::app);
        if (!f) {
            die("Can't write file: " + path);
        }
        f << val << endl;
        return;
    }

    Node* root = parse(tokens);
    if (root) elevate(root);
}
std::set<string> imported;              // 记录已经导入过的文件
void loadFile(string path) {
    if (imported.count(path)) return;   // 导入过就跳过
    imported.insert(path);

    ifstream fin(path.c_str());
    if (!fin) {
        cerr << "打不开文件: " << path << endl;
        exit(1);
    }
    string line;
    int lineNo = 0;
    while (getline(fin, line)) {
        lineNo++;
        vector<string> t = tokenize(line);
        if (!t.empty() && t[0] == "import") {
            string lib = t[1].substr(1, t[1].size() - 2);
            if (lib.substr(0,5) == "jm://") {
                lib = lib.substr(5);
                if (!imported.count(lib)) download_lib_on_web(lib);
            } else {
                ifstream fin2(lib.c_str());
                if (!fin2) {
                    download_lib_on_web(lib);
                }
            }
            loadFile(lib);
            continue;
        }
        loaded.push_back(line);
        srcFile.push_back(path);
        srcLine.push_back(lineNo);
    }
}
int main(int argc, char* argv[]) {
    init();
    frames.push_back({});
    frames.back()["__fid"] = num(++frameSerial);
    string file;
    if (argc < 2) {
        cin >> file;
    } else {
        file = argv[1];
    }
    ifstream fin(file.c_str());
    if (!fin) {
        cerr << "Can't open file: " << file << endl;
        return 1;
    }

    string line;
    loadFile(file);

    string curClass = "";
    int classLine = -1;

    for (int lineCo = 0; lineCo < loaded.size(); lineCo++) {
        vector<string> t = tokenize(loaded[lineCo]);
        string first = t.empty() ? "" : t[0];

        if (!first.empty() && first[0] == ':') {
            labels[first.substr(1)] = lineCo;
        }
        if (first == "while" || first == "if") {
            st.push(lineCo);
        }
        if (first == "while") loopSt.push_back(lineCo);
        if (first == "struct") {
            if (t.size() < 2) die("usage: struct Name field1 field2 ...", lineCo);
            structs[t[1]] = vector<string>(t.begin() + 2, t.end());
        }
        if (first == "class") {
            if (curClass != "") die("class inside class", lineCo);
            st.push(lineCo);
            curClass = t[1];
            classLine = lineCo;
            classes.insert(t[1]);
            if (t.size() >= 4 && t[2] == "extends") parentOf[t[1]] = t[3];
        }
        if (first == "func") {
            st.push(lineCo);
            if (curClass == "") funcs[t[1]] = lineCo;
            else funcs[curClass + "." + t[1]] = lineCo;      // 方法名:类名.方法名
            loopSt.push_back(-1);
        }
        if (first == "break" || first == "continue") {
            if (loopSt.empty() || loopSt.back() == -1) die(first + " not in while", lineCo);
            loopOf[lineCo] = loopSt.back();
        }
        if (first == "else") {
            if (st.empty() || tokenize(loaded[st.top()])[0] != "if") die("else without if", lineCo);
            int ifLine = st.top();
            elseOf[ifLine] = lineCo;
            elseIf[lineCo] = ifLine;
        }
        if (first == "end") {
            if (st.empty()) die("Extra end", lineCo);
            int tp = st.top();
            st.pop();
            string opener = tokenize(loaded[tp])[0];
            if (opener == "while" || opener == "func") loopSt.pop_back();
            labels2[tp] = lineCo;
            labels2[lineCo] = tp;
            if (tp == classLine) { curClass = ""; classLine = -1; }
        }
    }

    if (!st.empty()) die("Block has no end", st.top());

    // run
    int pc = 0;
    while (pc < loaded.size()) {
        int next = pc + 1;
        run(loaded[pc], next);
        pc = next;
    }
}