Skip to content

Commit

Permalink
Merge pull request #132 from angelcaru/mypy-2
Browse files Browse the repository at this point in the history
Add type annotations to `core/builtin_funcs.py` and `core/builtin_classes/base_classes.py`
  • Loading branch information
Almas-Ali authored May 3, 2024
2 parents fc16027 + d025f24 commit a9d13be
Show file tree
Hide file tree
Showing 9 changed files with 429 additions and 290 deletions.
102 changes: 67 additions & 35 deletions core/builtin_classes/base_classes.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
from __future__ import annotations

from core.errors import *
from core.datatypes import *
from core.parser import RTResult
from core.builtin_funcs import BuiltInFunction
from core.builtin_funcs import BuiltInFunction, args

from typing import Callable, Any


class BuiltInClass(BaseClass):
def __init__(self, name, instance_class):
instance_class: BuiltInObjectMeta

def __init__(self, name: str, instance_class: BuiltInObjectMeta) -> None:
super().__init__(name, instance_class.__symbol_table__)
self.instance_class = instance_class

def create(self, args):
inst = BuiltInInstance(self)
return RTResult().success(inst.set_context(self.context).set_pos(self.pos_start, self.pos_end))
def create(self, args: list[Value]) -> RTResult[BaseInstance]:
inst = BuiltInInstance(self, self.instance_class(self))
return RTResult[BaseInstance]().success(inst.set_context(self.context).set_pos(self.pos_start, self.pos_end))

def init(self, inst, args, kwargs):
res = RTResult()
def init(self, inst: BaseInstance, args: list[Value], kwargs: dict[str, Value]) -> RTResult[None]:
res = RTResult[None]()
if len(kwargs) > 0:
return res.failure(
RTError(
Expand All @@ -24,43 +30,61 @@ def init(self, inst, args, kwargs):
list(kwargs.values())[0].context,
)
)
_, error = inst.operator("__constructor__", args)
_, error = inst.operator("__constructor__", *args)
if error:
return res.failure(error)
return res.success(None)

def get(self, name):
return None, self.illegal_operation(name)
def get(self, name: str) -> Optional[Value]:
return self.symbol_table.get(name)

def __repr__(self):
def __repr__(self) -> str:
return f"<built-in class {self.name}>"


class BuiltInInstance(BaseInstance):
def __init__(self, parent_class):
obj: BuiltInObject

def __init__(self, parent_class: BuiltInClass, obj: BuiltInObject) -> None:
super().__init__(parent_class, parent_class.instance_class.__symbol_table__)
self.instance_class = parent_class.instance_class
self.obj = obj
self.symbol_table.set("this", self)

def operator(self, operator, *args):
def bind_method(self, method: BaseFunction) -> RTResult[BaseFunction]:
assert isinstance(method, BuiltInFunction)
assert method.func is not None

@args(method.func.arg_names, method.func.defaults)
def new_func(ctx: Context) -> RTResult[Value]:
assert method.func is not None
return method.func(self.obj, ctx)

return RTResult[BaseFunction]().success(BuiltInFunction(method.name, new_func))

def operator(self, operator: str, *args: Value) -> ResultTuple:
try:
op = self.instance_class.__operators__[operator]
op = type(self.obj).__operators__[operator]
except KeyError:
return None, self.illegal_operation(*args)
res = RTResult()
value = res.register(op(self, *args))
res = RTResult[Value]()
value = res.register(op(self.obj, list(args)))
if res.should_return():
assert res.error is not None
return None, res.error
assert value is not None
return value, None


class BuiltInObjectMeta(type):
def __new__(cls, class_name, bases, attrs):
__symbol_table__: SymbolTable
__operators__: dict[str, Callable[[BuiltInObject, list[Value]], RTResult[Value]]]

def __new__(cls, class_name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> BuiltInObjectMeta:
if class_name == "BuiltInObject":
return type.__new__(cls, class_name, bases, attrs)

operators = {}
symbols = {}
symbols: dict[str, Value] = {}
for name, value in attrs.items():
if hasattr(value, "__operator__"):
operators[value.__operator__] = value
Expand All @@ -77,25 +101,33 @@ def __new__(cls, class_name, bases, attrs):


class BuiltInObject(metaclass=BuiltInObjectMeta):
pass
parent_class: BuiltInClass

def __init__(self, parent_class: BuiltInClass) -> None:
self.parent_class = parent_class


# Decorators for methods and operators
def operator(dunder):
def _deco(f):
f.__operator__ = dunder
C = TypeVar("C", bound=Callable)


def operator(dunder: str) -> Callable[[C], C]:
def _deco(f: C) -> C:
f.__operator__ = dunder # type: ignore
return f

return _deco


def method(f):
f.__is_method__ = True
def method(f: C) -> C:
f.__is_method__ = True # type: ignore
return f


# Decorator to check argument types
def check(types, defaults=None):
def check(
types: list[type[Value]], defaults: Optional[list[Optional[Value]]] = None
) -> Any: # return type == "idk figure it out"
if defaults is None:
defaults = [None] * len(types)

Expand All @@ -110,20 +142,20 @@ def wrapper(self, args):
if len(args) > len(types):
return res.failure(
RTError(
self.pos_start,
self.pos_end,
self.parent_class.pos_start,
self.parent_class.pos_end,
f"{len(args) - len(types)} too many args passed into {full_func_name}",
self.context,
self.parent_class.context,
)
)

if len(args) < len(types) - len(list(filter(lambda default: default is not None, defaults))):
return res.failure(
RTError(
self.pos_start,
self.pos_end,
self.parent_class.pos_start,
self.parent_class.pos_end,
f"{(len(types) - len(list(filter(lambda default: default is not None, defaults)))) - len(args)} too few args passed into {full_func_name}",
self.context,
self.parent_class.context,
)
)

Expand All @@ -135,10 +167,10 @@ def wrapper(self, args):
if not isinstance(arg, typ):
return res.failure(
RTError(
self.pos_start,
self.pos_end,
self.parent_class.pos_start,
self.parent_class.pos_end,
f"Expected {typ.__name__} for argument {i} (0-based) of {full_func_name}, got {arg.__class__.__name__} instead",
self.context,
self.parent_class.context,
)
)
real_args.append(arg)
Expand Down
44 changes: 23 additions & 21 deletions core/builtin_classes/file_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
from core.builtin_funcs import args
from core.builtin_classes.base_classes import BuiltInObject, operator, check, method

from typing import IO


class FileObject(BuiltInObject):
file: IO[str]

@operator("__constructor__")
@check([String, String], [None, String("r")])
def constructor(self, path, mode):
def constructor(self, path: String, mode: String) -> RTResult[Value]:
allowed_modes = [None, "r", "w", "a", "r+", "w+", "a+"] # Allowed modes for opening files
res = RTResult()
res = RTResult[Value]()
if mode.value not in allowed_modes:
return res.failure(RTError(mode.pos_start, mode.pos_end, f"Invalid mode '{mode.value}'", mode.context))
try:
Expand All @@ -19,22 +23,22 @@ def constructor(self, path, mode):
return res.failure(
RTError(path.pos_start, path.pos_end, f"Could not open file {path.value}: {e}", path.context)
)
return res.success(None)
return res.success(Null.null())

@args(["count"], [Number(-1)])
@method
def read(ctx):
res = RTResult()
self = ctx.symbol_table.get("this")
def read(self, ctx: Context) -> RTResult[Value]:
res = RTResult[Value]()
count = ctx.symbol_table.get("count")
assert count is not None
if not isinstance(count, Number):
return res.failure(RTError(count.pos_start, count.pos_end, "Count must be a number", count.context))

try:
if count.value == -1:
value = self.file.read()
else:
value = self.file.read(count.value)
value = self.file.read(int(count.value))
return res.success(String(value))
except OSError as e:
return res.failure(
Expand All @@ -44,7 +48,7 @@ def read(ctx):
@args([])
@method
def readline(ctx):
res = RTResult()
res = RTResult[Value]()
self = ctx.symbol_table.get("this")
try:
value = self.file.readline()
Expand All @@ -54,21 +58,21 @@ def readline(ctx):

@args([])
@method
def readlines(ctx):
res = RTResult()
self = ctx.symbol_table.get("this")
def readlines(self, ctx: Context) -> RTResult[Value]:
res = RTResult[Value]()
try:
value = self.file.readlines()
return res.success(Array([String(line) for line in value]))
except OSError as e:
return res.failure(RTError(None, None, f"Could not read from file: {e.strerror}", None))
pos = Position(-1, -1, -1, "<idk>", "<idk>")
return res.failure(RTError(pos, pos, f"Could not read from file: {e.strerror}", ctx))

@args(["data"])
@method
def write(ctx):
res = RTResult()
self = ctx.symbol_table.get("this")
def write(self, ctx: Context) -> RTResult[Value]:
res = RTResult[Value]()
data = ctx.symbol_table.get("data")
assert data is not None
if not isinstance(data, String):
return res.failure(RTError(data.pos_start, data.pos_end, "Data must be a string", data.context))

Expand All @@ -82,15 +86,13 @@ def write(ctx):

@args([])
@method
def close(ctx):
res = RTResult()
self = ctx.symbol_table.get("this")
def close(self, _ctx: Context) -> RTResult[Value]:
res = RTResult[Value]()
self.file.close()
return res.success(Null.null())

@args([])
@method
def is_closed(ctx):
res = RTResult()
self = ctx.symbol_table.get("this")
def is_closed(self, _ctx: Context) -> RTResult[Value]:
res = RTResult[Value]()
return res.success(Boolean(self.file.closed))
16 changes: 10 additions & 6 deletions core/builtin_classes/json_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
class JSONObject(BuiltInObject):
@operator("__constructor__")
@check([], [])
def constructor(self):
return RTResult().success(None)
def constructor(self) -> RTResult[Value]:
return RTResult[Value]().success(Null.null())

@args(["radon_object"])
@method
def dumps(ctx):
res = RTResult()
def dumps(self, ctx: Context) -> RTResult[Value]:
res = RTResult[Value]()
radon_object = ctx.symbol_table.get("radon_object")
assert radon_object is not None
try:
return res.success(String(json.dumps(deradonify(radon_object))))
except Exception as e:
Expand All @@ -28,9 +29,12 @@ def dumps(ctx):

@args(["radon_string"])
@method
def loads(ctx):
res = RTResult()
def loads(self, ctx: Context) -> RTResult[Value]:
res = RTResult[Value]()
radon_string = ctx.symbol_table.get("radon_string")
assert radon_string is not None
if not isinstance(radon_string, String):
return res.failure(RTError(radon_string.pos_start, radon_string.pos_end, "Cannot loads a non-string", ctx))
try:
return res.success(
radonify(
Expand Down
Loading

0 comments on commit a9d13be

Please sign in to comment.