nodespeak 0.2.1

A JIT-ish compiler for number-crunching applications.
Documentation
#!/bin/env python3
import sys

class Enum:
    def __init__(self):
        self.name = ''
        self.items = []
    
    def add_item(self, item):
        self.items.append(item)
    
    def tree(self, indent=0):
        ind = '  ' * indent
        out = self.name + '(\n'
        for item in self.items:
            out += ind + '  '
            if type(item) is str:
                out += item + ',\n'
            else:
                out += item.tree(indent + 1) + ',\n'
        out += ind + ')'
        return out
    
    def get(self, index):
        return self.items[index]
    
    def keys(self):
        return list(range(len(self.items)))

class Struct:
    def __init__(self):
        self.name = ''
        self.items = {}
    
    def add_item(self, name, value):
        self.items[name] = value
    
    def tree(self, indent=0):
        ind = '  ' * indent
        out = self.name + ' {\n'
        for key, item in self.items.items():
            out += ind + '  ' + key + ': '
            if type(item) is str:
                out += item + ',\n'
            else:
                out += item.tree(indent + 1) + ',\n'
        out += ind + '}'
        return out
    
    def get(self, index):
        return self.items[index]
    
    def keys(self):
        return list(self.items.keys())

class Array:
    def __init__(self):
        self.items = []

    def add_item(self, item):
        self.items.append(item)
    
    def tree(self, indent=0):
        ind = '  ' * indent
        out = '[\n' 
        for item in self.items:
            out += ind + '  '
            if type(item) is str:
                out += item + ',\n'
            else:
                out += item.tree(indent + 1) + ',\n'
        out += ind + ']'
        return out
    
    def get(self, index):
        return self.items[index]
    
    def keys(self):
        return list(range(len(self.items)))

buffer = sys.stdin.read()
lines = [line for line in buffer.split('\n') if line not in ['Compiling source...']]

mode_stack = [None]
data_stack = []

def push_new(symbol, name):
    if symbol == '{':
        mode_stack.append('struct')
        to_push = Struct()
        to_push.name = name
        data_stack.append(to_push)
    elif symbol == '(':
        mode_stack.append('enum')
        to_push = Enum()
        to_push.name = name
        data_stack.append(to_push)
    elif symbol == '[':
        mode_stack.append('array')
        to_push = Array()
        data_stack.append(to_push)
    else:
        raise Exception('Unknown data symbol ' + symbol + ' (name ' + name + ')')

def pop():
    if (len(data_stack) > 1):
        data_stack.pop()
        mode_stack.pop()

for line in lines:
    line = line.strip()
    mode = mode_stack[-1]
    if (len(line) <= 1):
        continue
    if mode == None:
        symbol = line[-1]
        name = line[:-1].strip()
        push_new(symbol, name)
    elif mode == 'struct':
        if ':' in line:
            key = line.split(':')[0]
            symbol = line[-1]
            name = line.split(':')[1][:-1].strip()
            if (symbol == ','):
                data_stack[-1].add_item(key, name)
            else:
                push_new(symbol, name)
                data_stack[-2].add_item(key, data_stack[-1])
        else:
            pop()
    elif mode == 'array':
        if '],' in line:
            pop()
        else:
            symbol = line[-1]
            name = line[:-1].strip()
            if symbol == ',':
                data_stack[-1].add_item(name)
            else:
                push_new(symbol, name)
                data_stack[-2].add_item(data_stack[-1])
    elif mode == 'enum':
        if '),' in line:
            pop()
        else:
            symbol = line[-1]
            name = line[:-1].strip()
            if symbol == ',':
                data_stack[-1].add_item(name)
            else:
                push_new(symbol, name)
                data_stack[-2].add_item(data_stack[-1])

root = data_stack[0]
        
class Variable:
    def __init__(self, source):
        self.defining_scope = -1
        self.name = 'UNNAMED'
        self.type_name = ''
        self.matching_scope = None
        type_declaration = source.get('data_type')
        if type(type_declaration) == type(''):
            self.type_name = type_declaration
            if self.type_name == 'Macro':
                self.matching_scope = int(source.get('initial_value').get(0).get('body').get(0))
        else:
            self.type_name = 'todo...'
    
    def describe(self):
        global scopes
        scope_name = 'nowhere'
        if self.defining_scope != -1:
            scope_name = 'in ' + scopes[self.defining_scope].get_name()
        return self.name + ' (' + self.type_name + ' defined ' + scope_name + ')' + self.extra()
    
    def extra(self):
        return ''
    
    def finalize(self):
        global scopes
        if self.matching_scope is not None:
            scopes[self.matching_scope].name = self.name

variables = []
for variable_source in root.get('variables').items:
    variable = Variable(variable_source)
    variables.append(variable)

class Scope:
    def __init__(self):
        self.name = 'unnamed'
        self.parent = None
        self.body = []
    
    def get_name(self):
        global scopes
        if self.parent is None:
            return self.name
        else:
            return scopes[self.parent].get_name() + '.' + self.name
        
    def describe(self):
        out = 'SCOPE for ' + self.get_name() + '\n'
        out += ' body:\n'
        for call in self.body:
            out += '  ' + call.describe().replace('\n', '\n  ') + '\n'
        return out

class MacroCall:
    def __init__(self, macro, inputs, outputs):
        self.macro = macro
        self.inputs = inputs
        self.outputs = outputs
    
    def describe(self):
        global variables
        out = 'call ' + variables[self.macro].describe() + '\n'
        for index, value in enumerate(self.inputs):
            out += '  i' + str(index + 1) + ': ' + variables[value].describe() + '\n'
        for index, value in enumerate(self.outputs):
            out += '  o' + str(index + 1) + ': ' + variables[value].describe() + '\n'
        return out[:-1] # Strip trailing whitespace.


scopes = []
scope_sources = root.get('scopes').items
current_index = -1
for scope_source in scope_sources:
    current_index += 1
    scope = Scope()
    if current_index == 0:
        scope.name = 'root'
    parent_source = scope_source.get('parent')
    if parent_source != 'None':
        scope.parent = int(parent_source.get(0).get(0))
    scopes.append(scope)
    if scope_source.get('body') != '[]':
        for call in scope_source.get('body').items:
            macro = int(call.get('macro').get(0))
            inputs, outputs = [], []
            if call.get('inputs') != '[]':
                for iinput in call.get('inputs').items:
                    # TODO: Properly handle var access objects.
                    inputs.append(int(iinput.get('base').get(0)))
            if call.get('outputs') != '[]':
                for output in call.get('outputs').items:
                    # TODO: Properly handle var access objects.
                    outputs.append(int(output.get('base').get(0)))
            scope.body.append(MacroCall(macro, inputs, outputs))
    if type(scope_source.get('symbols')) is not str:
        for symbol in scope_source.get('symbols').keys():
            real_name = symbol[1:-1] # Strip quotations.
            variable_id = scope_source.get('symbols').get(symbol).get(0)
            index = int(variable_id)
            variables[index].name = real_name
            variables[index].defining_scope = current_index
    if (scope_source.get('intermediates') == '[]'):
        continue
    for iindex, intermediate in enumerate(scope_source.get('intermediates').items):
        variable_id = intermediate.get(0)
        index = int(variable_id)
        variables[index].name = '!intermediate_' + str(iindex)
        variables[index].defining_scope = current_index

for variable in variables:
    variable.finalize()
for scope in scopes:
    print(scope.describe())