view interps/glass/glass.cc @ 12500:e48c08805365 draft default tip

<b_jonas> ` learn \'The password of the month is Cthulhuquagdonic Mothraquagdonic Narwhalicorn.\' # https://logs.esolangs.org/libera-esolangs/2024-04.html#lKE Infinite craft
author HackEso <hackeso@esolangs.org>
date Wed, 01 May 2024 06:39:10 +0000
parents 859f9b4339e6
children
line wrap: on
line source

/*
 * Copyright (c) 2005  Gregor Richards
 *
 * This file is part of Glass.
 * 
 * Glass is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * Glass is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Glass; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <iostream>
using namespace std;

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "builtins.h"
#include "func.h"
#include "glass.h"
#include "klass.h"
#include "klassi.h"
#include "parseq.h"
#include "variable.h"

map<string,Variable *> globalVars;

vector<KlassI *> globalInstant;

deque<Variable *> mainStack;

#ifdef IRC
string IRC_o, chan;
unsigned int progTimer;
#define MAXTIME 10000000
void keepalivePing(int ignore);
#define PINGRATE (60*5)
#endif

bool parseI(char *inp, int &i, ParseQ *pq);
void stringParse(string &toparse);
void cacheIt(const char *cachef);
void runIt();
void clearM();

int main(int argc, char **argv, char **envp)
{
    char *input, *bins;
    FILE *fin;
    int fini;
    struct stat sbuf;
    int i = 0, j;
    ParseQ *master;
    string eval;
    map<string,Variable *>::iterator svari;
    
    // first read in builtins
    bins = strdup(builtinDefinitions);
    if (bins) {
        master = new ParseQ();
        i = 0;
        while (parseI(bins, i, master));
        master->parseKlasses();
        delete master;
        free(bins);
    }
    
#ifndef IRC
    if (argc < 2) {
        fprintf(stderr, "Use: glass [cache] <input>\n");
        return 1;
    }
    
    // non-IRC gets environment variables
    for (i = 0; envp[i]; i++) {
        for (j = 0; envp[i][j] && envp[i][j] != '='; j++);
        if (!envp[i][j]) continue;
        eval = envp[i];
        globalVars["Env" + eval.substr(0, j)] = new Variable(VAR_STRING, eval.substr(j + 1));
    }
        
#else
    if (argc < 5) {
        cout << "Use: glass <cachefile> <username> <master> <channel>" << endl;
        return 1;
    }
#endif
    
    for (j = 1; argv[j]; j++) {
        // open the file ...
        fin = fopen(argv[j], "r");
        if (!fin) continue;
        
        // get the size ...
        fini = fileno(fin);
        if (fstat(fini, &sbuf) == -1) continue;
        
        // allocate
        input = (char *) malloc(sbuf.st_size + 2);
        input[sbuf.st_size] = '\0';
        
        // read
        if (fread(input, 1, sbuf.st_size, fin) < 0) continue;
        
        // parse
        master = new ParseQ();
        i = 0;
        while (parseI(input, i, master));
        master->parseKlasses();
        delete master;
        
        // free
        free(input);
        
        // if this is our cache file, ignore the M
        if (j == 1 && argv[2]) {
            clearM();
        }
    }
    
#ifndef IRC
    // cache if necessary
    if (argv[2]) cacheIt(argv[1]);
    
    // if we're not in IRC mode, this is the file to run
    runIt();
#else
    // IRC
    
    char line[32257];
    string sline;
    line[32256] = '\0';
    int mode = 0;
    chan = argv[4];
    
    // schedule keepalive pings
    signal(14, keepalivePing);
    alarm(PINGRATE);
    
    // fix our cache (which will probably have a faulty M in it)
    clearM();
    
    while (true) {
        line[0] = '\0';
        while (!fgets(line, 1024, stdin)) {
            // we can't use sleep() directly due to alarm()
            int pid = fork();
            if (pid == 0) {
                sleep(1);
                exit(0);
            } else if (pid > 0) {
                wait(NULL);
            }
        }
        sline = line;
        cout << sline << endl;
        
        if (sline.substr(0, 5) == "PING ") {
            cerr << "PONG :localhost\r\n";
            continue;
        }
        
        switch (mode) {
            case 0:
                // tell it our nick
                cerr << "USER " << argv[2] << " localhost localhost :GlassBot\r\n";
                cerr << "NICK " << argv[2] << "\r\n";
                mode++;
                break;
                
            case 1:
                // join the channel
                cerr << "JOIN #" << chan << "\r\n";
                mode++;
                break;
                
            case 2:
                // the real heavy lifting
                if (sline[0] == ':') {
                    unsigned int ui;
                    unsigned int gloc = 0;
                    string sender, retsend;

                    // parse for the !
                    for (ui = 0; ui < sline.length() && sline[ui] != '!' && sline[ui] != ' '; ui++);
                    if (ui == sline.length()) continue;
                    // get the sender
                    sender = sline.substr(1, ui - 1);
                    
                    // parse for the space
                    for (; ui < sline.length() && sline[ui] != ' '; ui++);
                    if (ui == sline.length()) continue;
                    sline = sline.substr(ui + 1);
                    
                    // it should be "PRIVMSG <something> :G!..."
                    if (sline.substr(0, 8) == "PRIVMSG " &&
                        (gloc = sline.find(":G!", 0)) != string::npos) {
                        // where should we return it to?
                        retsend = sline.substr(8, gloc - 9);
                        if (retsend[0] != '#') {
                            // send it to the sender
                            retsend = sender;
                        }
                        
                        // we only need what's after G!
                        sline = sline.substr(gloc + 3);
                        strcpy(line, sline.c_str());
                        
                        // maybe it's a URL
                        if (sline[0] == 'U') {
                            sline = sline.substr(1);
                            strcpy(line, sline.c_str());
                            
                            // make sure the URL is good
                            for (ui = 0; line[ui]; ui++) {
                                switch (line[ui]) {
                                    case '\r':
                                    case '\n':
                                    case '?':
                                        line[ui] = '\0';
                                        break;
                                }
                            }
                            if (strncmp(line, "http://", 7)) {
                                line[0] = '\0';
                            }
                            
                            // fork off a pid to do lynx --dump
                            int urli[2], pid;
                            pipe(urli);
                            pid = fork();
                            if (pid == 0) {
                                dup2(urli[1], 1);
                                dup2(urli[1], 2);
                                execlp("lynx", "lynx", "--dump", line, 
                                       "-connect_timeout", "15", NULL);
                                exit(0);
                            } else if (pid >= 0) {
                                read(urli[0], line, 32256);
                                wait(NULL);
                            }
                            close(urli[0]);
                            close(urli[1]);
                        }
                        
                        // we now have our input, parse!
                        master = new ParseQ();
                        i = 0;
                        while (parseI(line, i, master));
                        master->parseKlasses();
                        delete master;
                        
                        // reset any destroyed builtins
                        bins = strdup(builtinDefinitions);
                        if (bins) {
                            master = new ParseQ();
                            i = 0;
                            while (parseI(bins, i, master));
                            master->parseKlasses();
                            delete master;
                            free(bins);
                        }
                        
                        cacheIt(argv[1]);
                        runIt();
                        
                        // display the output
                        if (IRC_o != "") {
                            // get rid of possible injections
                            for (ui = 0; ui < IRC_o.length(); ui++) {
                                switch (IRC_o[ui]) {
                                    case '\n':
                                    case '\r':
                                        IRC_o[ui] = ' ';
                                        break;
                                }
                            }
                            alarm(0);
                            // allow our master to do raw sending
                            if (sender == argv[3] && IRC_o[0] == '^') {
                                cerr << IRC_o.substr(1) << "\r\n";
                            } else {
                                // max of 400 chars (about)
                                if (IRC_o.length() > 400) {
                                    IRC_o = IRC_o.substr(0, 393) + " Flood!";
                                }
                                cerr << "PRIVMSG " << retsend << " :" << IRC_o << "\r\n";
                            }
                            alarm(PINGRATE);
                        }
                        
                        // then garbage collection
                        for (ui = 0; ui < globalInstant.size(); ui++) {
                            delete globalInstant[ui];
                        }
                        globalInstant.clear();
                        for (ui = 0; ui < mainStack.size(); ui++) {
                            delete mainStack[ui];
                        }
                        mainStack.clear();
                        clearM();
                        /* we have to keep a vector of strings to properly erase globalVars,
                         * otherwise we'll end up messing up the iterator mid-loop */
                        vector<string> todel;
                        for (svari = globalVars.begin(); svari != globalVars.end(); svari++) {
                            if (svari->second->type != VAR_KLASS) {
                                // any user global variables are deleted
                                delete svari->second;
                                todel.push_back(svari->first);
                            }
                        }
                        for (ui = 0; ui < todel.size(); ui++) {
                            globalVars.erase(todel[ui]);
                        }
                        todel.clear();
                    }
                }
                break;
        }
    }
#endif
    
    return 0;
}

bool parseI(char *inp, int &i, ParseQ *pq)
{
    int j;
    
    // this function stops iterating at inp[i] == '\0'
    if (!inp[i]) return false;
    
    if (inp[i] >= 'A' && inp[i] <= 'Z') {
        pq->add(new ParseQElement(PQT_GLOBAL, inp[i]));
    } else if (inp[i] >= 'a' && inp[i] <= 'z') {
        pq->add(new ParseQElement(PQT_CLASSWIDE, inp[i]));
    } else if (inp[i] >= '0' && inp[i] <= '9') {
        pq->add(new ParseQElement(PQT_STACK, inp[i]));
    } else if (inp[i] == '(') {
        i++;
        for (j = i; inp[j] && inp[j] != ')'; j++);
        if (!inp[j]) {
            // ERROR
            return false;
        }
        
        // now we're at the )
        inp[j] = '\0';
        // now some sub-parsing
        if (inp[i] >= 'A' && inp[i] <= 'Z') {
            pq->add(new ParseQElement(PQT_GLOBAL, inp + i));
        } else if (inp[i] >= 'a' && inp[i] <= 'z') {
            pq->add(new ParseQElement(PQT_CLASSWIDE, inp + i));
        } else if (inp[i] >= '0' && inp[i] <= '9') {
            pq->add(new ParseQElement(PQT_STACK, inp + i));
        } else if (inp[i] == '_') {
            pq->add(new ParseQElement(PQT_LOCAL, inp + i));
        }
        i = j;
    } else if (inp[i] == '"') {
        // push a string
        i++;
        for (j = i; inp[j] && inp[j] != '"'; j++);
        if (!inp[j]) {
            // ERROR
            return false;
        }
        
        // now we're on the ending "
        inp[j] = '\0';
        string tomk = inp + i;
        
        stringParse(tomk);
        
        pq->add(new ParseQElement(PQT_STRING, tomk));
        i = j;
    } else if (inp[i] == '<') {
        // push a number
        i++;
        for (j = i; inp[j] && inp[j] != '>'; j++);
        if (!inp[j]) {
            // ERROR
            return false;
        }
        
        // now we're on the >
        inp[j] = '\0';
        pq->add(new ParseQElement(PQT_NUMBER, inp + i));
        i = j;
    } else if (inp[i] == '~') {
        // special, builtin
        i++;
        for (j = i; inp[j] && inp[j] != '~'; j++);
        if (!inp[j]) {
            // ERROR
            return false;
        }
        
        // now we're on the ending "
        inp[j] = '\0';
        pq->add(new ParseQElement(PQT_BUILTIN, inp + i));
        i = j;
    } else if (inp[i] == '\'') {
        // comment
        i++;
        for (; inp[i] && inp[i] != '\''; i++);
        if (!inp[i]) {
            // ERROR
            return false;
        }
    } else if (inp[i] != ' ' && inp[i] != '\t' &&
               inp[i] != '\n' && inp[i] != '\r') {
        // the rest are all commands (or ignorable non-commands)
        pq->add(new ParseQElement(PQT_COMMAND, inp[i]));
    }
    i++;
    return true;
}

void stringParse(string &toparse)
{
    unsigned int i;
    
    for (i = 0; i < toparse.length(); i++) {
        if (toparse[i] == '\\') {
            switch (toparse[i+1]) {
                case 'n':
                    toparse.replace(i, 2, string("\n"));
                    break;
                    
                default:
                    toparse.replace(i, 2, toparse.substr(i + 1, 1));
            }
        }
    }
}

Variable *getVar(KlassI *klass, map<string,Variable *> *locals, string name)
{
    // what type of var?
    char fchar = name[0];
    if (fchar >= 'A' && fchar <= 'Z') {
        // global
        if (globalVars.find(name) == globalVars.end()) {
            globalVars[name] = new Variable();
        }
        return globalVars[name];
    } else if (fchar >= 'a' && fchar <= 'z') {
        // classwide
        if (klass->vars.find(name) == klass->vars.end()) {
            klass->vars[name] = new Variable();
            // check if it's a function that hasn't been allocated in the klassi due to lazy allocation
            if (klass->of->functions.find(name) != klass->of->functions.end()) {
                klass->vars[name]->type = VAR_FUNC;
                klass->vars[name]->fval = klass->of->functions[name];
            }
        }
        return klass->vars[name];
    } else if (fchar == '_') {
        // local
        if (locals->find(name) == locals->end()) {
            (*locals)[name] = new Variable();
        }
        return (*locals)[name];
    } else {
        // ERROR
        exit(2);
    }
}

#ifdef IRC
void keepalivePing(int ignore)
{
    cerr << "PING :localhost\r\n";
    alarm(PINGRATE);
}
#endif


void cacheIt(const char *cachef)
{
    FILE *fin;
    map<string,Variable *>::iterator svari;
    
    // cache it
    fin = fopen(cachef, "w");
    if (fin) {
        Klass *curklass;
        map<string,Func *>::iterator fvari;
        for (svari = globalVars.begin(); svari != globalVars.end(); svari++) {
            // for each klass ...
            if (svari->second->type == VAR_KLASS) {
                // write the name ...
                fputs("{(", fin);
                fputs(svari->first.c_str(), fin);
                fputs(")", fin);
                curklass = svari->second->kval;
                // for each function ...
                for (fvari = curklass->functions.begin(); fvari !=
                     curklass->functions.end(); fvari++) {
                    string cont;
                    // write the name ...
                    fputs("[(", fin);
                    fputs(fvari->second->name.c_str(), fin);
                    fputs(")", fin);
                    // and the content
                    cont = fvari->second->contents->dump();
                    fputs(cont.c_str(), fin);
                    // and close it
                    fputs("]", fin);
                }
                // close it
                fputs("}", fin);
            }
        }
        fclose(fin);
    }
}

void runIt()
{
    // now run M.m
    Variable *Mvar;
    Klass *M;
    KlassI *MI;
    if (globalVars.find("M") == globalVars.end()) {
#ifndef IRC
        cout << "OK" << endl;
        return;
#else
        IRC_o = "";
        cerr << "PRIVMSG #" << chan << " :OK\r\n" << endl;
        return;
#endif
    }
    Mvar = globalVars["M"];
    
    if (Mvar->type != VAR_KLASS) {
#ifndef IRC
        cout << "OK" << endl;
        return;
#else
        IRC_o = "";
        cerr << "PRIVMSG #" << chan << " :OK\r\n" << endl;
        return;
#endif
    }
    
    M = Mvar->kval;
    MI = new KlassI();
    globalVars["MI"] = new Variable(VAR_KLASSI, MI);
    MI->of = M;
    
    // now make sure M.m exists
    if (M->functions.find("m") == M->functions.end()) {
        cout << "NO M.m!" << endl;
        return;
    }
    
    // and run it
#ifdef IRC
    IRC_o = "";
    progTimer = MAXTIME;
#endif
    M->functions["m"]->contents->runFunc(MI, M->functions["m"]);
}

void clearM()
{
    if (globalVars.find("MI") != globalVars.end()) {
        if (globalVars["MI"]->type == VAR_KLASSI) {
            delete globalVars["MI"]->kival;
            delete globalVars["MI"];
            globalVars.erase("MI");
        }
    }
    
    if (globalVars.find("M") != globalVars.end()) {
        if (globalVars["M"]->type == VAR_KLASS) {
            delete globalVars["M"]->kval;
            delete globalVars["M"];
            globalVars.erase("M");
        }
    }
}