Newer
Older
NewLang / main.py
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
# Copyright 2021 Jookia <contact@jookia.org>

def tokenizer(code):
    tokens = []
    token = ""
    text = ""
    mode = "normal" # normal/note/text
    for symbol in code:
        if symbol == " " or symbol == "\t" or symbol == "\n":
            lowered = token.lower()
            if lowered == "":
                pass
            elif lowered == "beginnote":
                mode = "note"
            elif lowered == "endnote":
                mode = "normal"
            elif lowered == "begintext":
                mode = "text"
            elif lowered == "endtext":
                tokens.append(("text", text[1:-8]))
                mode = "normal"
                text = ""
            elif token != "":
                if mode == "normal":
                    keywords = ["newlang", "done", "set", "to", "endset",
                                "if", "then", "else", "endif"]
                    if lowered in keywords:
                        type = "keyword"
                    else:
                        type = "symbol"
                    tokens.append((type, lowered))
            token = ""
        else:
            token += symbol
        if mode == "text":
            text += symbol
    return tokens

parser_tokens = None
parser_pos = 0

def parser_reset(tokens):
    global parser_pos
    global parser_tokens
    parser_tokens = tokens
    parser_pos = 0

def parser_eof():
    global parser_pos
    global parser_tokens
    return parser_pos >= len(parser_tokens)

def parser_next():
    global parser_pos
    global parser_tokens
    if parser_eof():
        print("Reached end of file early")
        sys.exit(1)
    (type, value) = parser_tokens[parser_pos]
    parser_pos += 1
    print("Read %s %s" % (type, value))
    return (type, value)

def parser_peek():
    global parser_pos
    global parser_tokens
    (type, value) = parser_tokens[parser_pos]
    print("Peeked %s %s" % (type, value))
    return (type, value)

def parser_skip():
    global parser_pos
    global parser_tokens
    parser_pos += 1

def parse_version():
    (type, value) = parser_next()
    if type != "keyword" or value != "newlang":
        print("Expected NewLang keyword")
        return None
    (type, value) = parser_next()
    print("Parsed language version %s" % (value))
    return value

def parse_subject():
    (type, value) = parser_next()
    if type != "symbol":
        print("Expected symbol, got %s" % (type))
        return None
    return value

def parse_verb():
    (type, value) = parser_next()
    if type != "symbol":
        print("Expected symbol, got %s" % (type))
        return None
    return value

def parse_arguments(terminator):
    args = []
    while True:
        (type, value) = parser_next()
        if type == "keyword":
            if value == terminator:
                return args
            else:
                print("Unexpected keyword %s" % (value))
                return None
        elif type == "text" or type == "symbol":
            args.append((type, value))
        else:
            print("Unexpected type %s" % (type))
            return None

def parse_statement(terminator):
    subject = parse_subject()
    if not subject:
        print("While parsing subject")
        return None
    verb = parse_verb()
    if not verb:
        print("While parsing verb")
        return None
    arguments = parse_arguments(terminator)
    if arguments is None:
        print("While parsing arguments")
        return None
    print("Parsed statement: subject %s verb %s args %s" % (subject, verb, arguments))
    return ('statement', subject, verb, arguments)

def parse_set():
    subject = parse_subject()
    if not subject:
        print("While parsing subject")
        return None
    (type, value) = parser_next()
    if type != "keyword" or value != "to":
        print("Expect to, got %s %s" % (type, value))
        return None
    print("Parsing set value...")
    ast = parse_statement("endset")
    if not ast:
        print("While parsing statement")
        return None
    print("Parsed set for %s" % (subject))
    return ('set', subject, ast)

def parse_if():
    print("Parsing if test condition...")
    test = parse_statement("then")
    if not test:
        print("While parsing test condition")
        return None
    print("Parsing if success statement...")
    success = parse_statement("else")
    if not success:
        print("While parsing success statement")
        return None
    print("Parsing if failure statement...")
    failure = parse_statement("endif")
    if not failure:
        print("While parsing failure statement")
        return None
    print("Parsed conditional")
    return ('if', test, success, failure)

def parse_directive():
    (type, value) = parser_peek()
    if type != "keyword" and type != "symbol":
        print("Expected keyword or symbol here, got %s" % (type))
        return None
    if type == "keyword":
        parser_skip()
        if value == "set":
            ast = parse_set()
            if not ast:
                print("While parsing set directive")
                return None
            return ast
        elif value == "if":
            ast = parse_if()
            if not ast:
                print("While parsing set directive")
                return None
            return ast
        else:
            print("Unexpected keyword %s" % (value))
            return None
    else:
        ast = parse_statement("done")
        if not ast:
            print("While parsing statement")
            return None
        return ast

def parse_file():
    print("Parsing file...")
    ast = []
    version = parse_version()
    if not version:
        print("While parsing version identifier at start of file")
        return None
    if version != "0":
        print("Invalid version identifier %s" % (version))
        return None
    while not parser_eof():
        directive = parse_directive()
        if directive == None:
            print("While parsing directive in file")
            return None
        else:
            ast.append(directive)
    print("Parsed file")
    return ast

def do_system_print(env, args):
    (text_type, text_value) = args[0]
    if text_type == "symbol":
        (text_type, text_value) = env[text_value]
    if text_type != "text":
        print("Invalid print value: %s" % (text_type))
        return None
    else:
        print(text_value)
        return None

def do_system_read(env, args):
    return ('text', input())

base_env = {
    "system": {
        "type": "module",
        "print": do_system_print,
        "read": do_system_read,
    }
}

def run_statement(env, ast):
    command = env[ast[1]][ast[2]]
    return command(env, ast[3])

def run_set(env, ast):
    env[ast[1]] = run_statement(env, ast[2])
    return env[ast[1]]

def run_if(env, ast):
    print("Unimplemented if")
    return None

def run_command(env, ast):
    type = ast[0]
    if type == "statement":
        return run_statement(env, ast)
    elif type == "set":
        return run_set(env, ast)
    elif type == "if":
        return run_if(env, ast)
    else:
        print("Unknown command type %s" % (ast))
        return None

def main(args):
    if len(args) != 2:
        print("Usage: main.py FILENAME")
        return 1
    filename = args[1]
    code = open(filename).read()
    if code[0:2] == '#!':
        next_line = code.find('\n') + 1
        code = code[next_line:]
    tokens = tokenizer(code)
    parser_reset(tokens)
    ast = parse_file()
    if not ast:
        return 1
    for command in ast:
        run_command(base_env, command)
    return 0

if __name__ == "__main__":
    import sys
    sys.exit(main(sys.argv))