OEIScript/implementation.py

From Esolang
Jump to navigation Jump to search

An implementation of OEIScript in Python 3. requests must be installed to use this interpreter, and an internet connection is required. Pass a file as an argument to execute it.

Implementation

#!/usr/bin/python
import sys
import requests
from enum import Enum
from collections import defaultdict

class Seq:
    def __init__(self, elems):
        self.elems = elems
        self.map = {}

    def __getitem__(self, key):
        if key < 0: return None
        try:
            if key in self.map:
                return self.map[key]
            else:
                return self.elems[key]
        except Exception:
            return None

    def __iter__(self):
        return iter(list(self.elems))

class Token(Enum):
    LINEBREAK = 0
    LBRACK = 1
    RBRACK = 2
    COLON = 3
    EQUALS = 4
    EXCLAM = 5
    QUESTION = 6
    NAME = 7
    INT = 8

def tokenize(text):
    tokens = []
    current = ""
    currentKind = None
    inComment = False
    for c in text:
        if inComment:
            if c == "\n":
                inComment = False
            else:
                continue
        done = False
        if currentKind == Token.INT and c in "0123456789":
            current += c
            done = True
        elif currentKind == Token.NAME and c.isalnum():
            current += c
            done = True
        elif currentKind in [Token.INT, Token.NAME]:
            tokens.append((currentKind, current))
            current = ""
            currentKind = None
        elif currentKind is None and c.isalnum():
            if c in "0123456789":
                currentKind = Token.INT
            else:
                currentKind = Token.NAME
            current = str(c)
            done = True
        if not done:
            if c == "\n": tokens.append((Token.LINEBREAK, None))
            elif c.isspace(): pass
            elif c == "{": tokens.append((Token.LBRACK, None))
            elif c == "}": tokens.append((Token.RBRACK, None))
            elif c == ":": tokens.append((Token.COLON, None))
            elif c == "=": tokens.append((Token.EQUALS, None))
            elif c == "!": tokens.append((Token.EXCLAM, None))
            elif c == "?": tokens.append((Token.QUESTION, None))
            elif c == "#": inComment = True
            else: raise Exception(f"Invalid character: {c}")
    return tokens

class Node:
    def __init__(self, parent):
        self.parent = parent
        self.contents = [[]]

def parse(tokens):
    node = Node(None)
    for t in tokens:
        if t[0] == Token.LBRACK:
            nxt = Node(node)
            node.contents[-1].append(nxt)
            node = nxt
        elif t[0] == Token.RBRACK:
            if node.parent is None:
                raise Exception("Unmatched brackets")
            node = node.parent
        elif t[0] == Token.LINEBREAK:
            if len(node.contents[-1]) != 0:
                node.contents.append([])
        else:
            node.contents[-1].append(t)
    if node.parent is not None:
        raise Exception("Unmatched brackets")
    return node

def evalchain(seqs, ctx):
    if len(seqs) == 0:
        return None
    if seqs[-1][0] == Token.NAME:
        val = ctx[seqs[-1][1]]
    elif seqs[-1][0] == Token.INT:
        val = int(seqs[-1][1])
    else:
        raise Exception(f"Invalid syntax: {seqs[-1]}")
    for s in reversed(seqs[:-1]):
        if val is None: return None
        if s[0] != Token.NAME:
            raise Exception(f"Invalid syntax: {s}")
        try:
            val = ctx[s[1]][val]
        except Exception:
            return None
    return val

def isAName(name):
    return len(name) >= 2 and name[0] == "A" and name[1:].isdigit()

def evaltree(lines, ctx, debug=False):
    for line in lines:
        if len(line) == 0: continue
        kinds = tuple((x[0] if isinstance(x, tuple) else Node) for x in line)
        if kinds == (Token.NAME, Token.COLON, Token.NAME):
            if isAName(line[2][1]):
                res = fetch(line[2][1])
                ctx[line[0][1]] = Seq(res)
                if debug: print(f"DEBUG: Loaded sequence {line[2][1]} to {line[0][1]}")
            else:
                raise Exception(f"Invalid syntax: {line[2][1]}")
        elif len(kinds) >= 2 and kinds[0] == Token.NAME and kinds[1] == Token.EQUALS:
            ctx[line[0][1]] = evalchain(line[2:], ctx)
            if debug: print(f"DEBUG: Saved chain result to {line[0][1]}")
        elif len(kinds) >= 1 and kinds[0] == Token.EXCLAM:
            res = evalchain(line[1:], ctx)
            if isinstance(res, int):
                print(res)
            if debug: print(f"DEBUG: Printed chain result")
        elif len(kinds) == 2 and kinds[0] == Token.NAME and kinds[1] == Token.QUESTION:
            try:
                ctx[line[0][1]] = int(input())
            except EOFError:
                ctx[line[0][1]] = None
            if debug: print(f"DEBUG: Input to {line[0][1]}")
        elif len(kinds) == 2 and kinds[0] == Token.NAME and kinds[1] == Node:
            if debug: print(f"DEBUG: Entering block based on {line[0][1]}")
            while ctx[line[0][1]] is not None:
                evaltree(line[1].contents, ctx, debug)
            if debug: print(f"DEBUG: Exiting block based on {line[0][1]}")
        else:
            raise Exception(f"Invalid syntax: {line}")


def fetch(name):
    res = requests.get(f"https://oeis.org/search?q=id:{name}&fmt=json")
    j = res.json()
    if j["count"] == 0:
        return ([], 0)
    return list(map(int, j["results"][0]["data"].split(",")))

def main():
    with open(sys.argv[1]) as infile:
        text = infile.read()
    res = parse(tokenize(text)).contents
    ctx = defaultdict(lambda: None)
    evaltree(res, ctx)


if __name__ == "__main__":
    main()