Snippets

Василий Шередеко nApA7q: Untitled snippet

Created by Василий Шередеко
#!/usr/bin/env python
from __future__ import annotations

import abc
import ast
import dataclasses
import os
import sys
from typing import Sequence, TypeVar, List, cast, Union

import multidict
import multimethod

T = TypeVar('T')


def unique_sequence(sequence: Sequence[T]) -> List[T]:
    seen = set()
    seen_add = seen.add
    return [x for x in sequence if not (x in seen or seen_add(x))]


# noinspection PyPep8Naming
class cached_property(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, cls=None):
        if instance is not None:
            result = instance.__dict__[self.func.__name__] = self.func(instance)
            return result
        return None  # ABC


@dataclasses.dataclass
class Position:
    # Line position in a document (one-based).
    line: int = 1

    # Character offset on a line in a document (one-based).
    column: int = 1

    @staticmethod
    def __add(lhs: int, rhs: int, min: int) -> int:
        """Compute max(min, lhs+rhs) (provided min <= lhs)."""
        return rhs + lhs if 0 < rhs or -rhs < lhs else min

    def lines(self, count: int = 1) -> Position:
        """(line related) Advance to the COUNT next lines."""
        if count:
            line = self.__add(self.line, count, 1)
            return Position(line, 1)
        return self

    def columns(self, count: int = 1) -> Position:
        """(column related) Advance to the COUNT next columns."""
        column = self.__add(self.column, count, 1)
        return Position(self.line, column)

    def __str__(self) -> str:
        return f"{self.line}:{self.column}"


@dataclasses.dataclass
class Location:
    # The location's filename
    filename: str

    # The location's begin position.
    begin: Position = Position()

    # The end's begin position.
    end: Position = Position()

    def step(self) -> Location:
        """Reset initial location to final location."""
        return Location(self.filename, self.end, self.end)

    def columns(self, count: int = 1) -> Location:
        """Extend the current location to the COUNT next columns."""
        end = self.end.columns(count)
        return Location(self.filename, self.begin, end)

    def lines(self, count: int = 1) -> Location:
        """Extend the current location to the COUNT next lines."""
        end = self.end.lines(count)
        return Location(self.filename, self.begin, end)

    def __add__(self, other: Location) -> Location:
        return Location(self.filename, self.begin, other.end)

    def __str__(self) -> str:
        if self.begin == self.end:
            return f"{self.filename}:{self.begin}"
        elif self.begin.line == self.end.line:
            return f"{self.filename}:{self.begin}-{self.end.column}"
        else:
            return f"{self.filename}:{self.begin}-{self.end}"


class Context:
    def __init__(self):
        self.__paths = unique_sequence([
            os.path.join(os.getcwd(), 'stdlib'),
            os.path.dirname(os.path.abspath(__file__))
        ])
        self.__filenames = {}
        self.__modules = {}

        print(self.__paths)

    @staticmethod
    def get_filename(module_name: str) -> str:
        filename = module_name.replace('.', os.path.sep) + '.orx'
        return filename

    @staticmethod
    def get_module_name(filename: str) -> str:
        module_name, _ = os.path.splitext(filename)
        module_name = module_name.replace(os.path.sep, '.')
        return module_name

    @cached_property
    def builtins_module(self) -> Module:
        return self.load('__builtins__')

    def open(self, filename: str, *, name: str = None) -> Module:
        fullname = os.path.abspath(filename)
        if fullname in self.__filenames:
            model = self.__filenames[fullname]
        else:
            with open(filename, 'r', encoding='utf-8') as stream:
                tree = ast.parse(stream.read(), filename)

            name = name or self.get_module_name(filename)
            model = Model(self, tree, name, filename)
            self.__filenames[fullname] = model
            model.analyze()
        return model.module

    def load(self, name: str) -> Module:
        filename = self.get_filename(name)
        for path in self.__paths:
            fullname = os.path.join(path, filename)
            if os.path.exists(fullname):
                return self.open(fullname, name=name)

        raise RuntimeError(f'Module `{name}` is not found')


class Model:
    def __init__(self, context: Context, tree: ast.Module, name: str, filename: str):
        self.context = context
        self.tree = tree
        self.name = name
        self.filename = filename
        self.__symbols = {}

    @property
    def module(self) -> Module:
        return cast(Module, self.get_symbol(self.tree))

    def get_symbol(self, node: ast.AST) -> Union[Member, Container]:
        if node not in self.__symbols:
            self.__symbols[node] = self.annotate_node(node)
        return self.__symbols[node]

    def analyze(self):
        # Define types
        for member in self.tree.body:
            if isinstance(member, ast.ClassDef):
                self.get_symbol(member)

    @multimethod.multimethod
    def annotate_node(self: Model, node: ast.AST):
        class_name = type(node).__name__
        raise NotImplementedError(f'Not found annotate method for class {class_name}')

    @multimethod.multimethod
    def annotate_node(self: Model, node: ast.Module) -> Module:
        return Module(self.context, self.name, self.filename)

    @multimethod.multimethod
    def annotate_node(self: Model, node: ast.ClassDef) -> Type:
        if self.module is self.context.builtins_module:
            if node.name == 'str':
                return StringType(self.module)
            elif node.name == 'int':
                return IntegerType(self.module)
            elif node.name == 'bool':
                return BooleanType(self.module)
        print(self.module)
        print(self.context.builtins_module)
        raise NotImplementedError


class Container:
    def __init__(self):
        self.__members = []
        self.__scope = multidict.MultiDict()

    @property
    def members(self) -> Sequence[Member]:
        return self.__members

    def add_member(self, member: Member):
        self.__members.append(member)
        self.__scope.add(member.name, member)

    def find_member(self, name: str) -> Union[None, Member, Sequence[Function]]:
        members = self.__scope.getall(name)
        if members:
            first_member = members[0]
            if isinstance(first_member, Function):
                return members
            return first_member
        return None


class Member(abc.ABC):
    @property
    @abc.abstractmethod
    def name(self) -> str:
        raise NotImplementedError

    @property
    @abc.abstractmethod
    def owner(self) -> Container:
        raise NotImplementedError

    @property
    def module(self) -> Module:
        if isinstance(self.owner, Module):
            return cast(Module, self.owner)
        return cast(Member, self.owner).module


class Module(Container):
    def __init__(self, context: Context, name: str, filename: str):
        super(Module, self).__init__()

        self.context = context
        self.name = name
        self.filename = filename
        self.owner = None

        self.__types = []
        self.__functions = []

    def __str__(self):
        return self.name

    def add_type(self, clazz: Type):
        self.__types.append(clazz)

    def add_function(self, func: Function):
        self.__functions.append(func)


class Type(Member, Container):
    def __init__(self, owner: Container, name: str):
        super(Type, self).__init__()

        self.__owner = owner
        self.__name = name

        self.owner.add_member(self)
        self.module.add_type(self)

    @property
    def name(self) -> str:
        return self.__name

    @property
    def owner(self) -> Container:
        return self.__owner


class BooleanType(Type):
    def __init__(self, owner: Module):
        super().__init__(owner, 'bool')


class IntegerType(Type):
    def __init__(self, owner: Module):
        super().__init__(owner, 'int')


class StringType(Type):
    def __init__(self, owner: Module):
        super().__init__(owner, 'str')


class Function(Member):
    def __init__(self, owner: Container, name: str):
        self.__name = name
        self.__owner = owner

        self.owner.add_member(self)
        self.module.add_function(self)

    @property
    def name(self) -> str:
        return self.__name

    @property
    def owner(self) -> Container:
        return self.__owner


def main():
    context = Context()
    module = context.open(sys.argv[1])
    print(module.name, module.filename)
    breakpoint()


if __name__ == '__main__':
    main()

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.