Skip to content

Commit

Permalink
feat: Add hashes namespace for inbuilt libs and http methods
Browse files Browse the repository at this point in the history
  • Loading branch information
FallenDeity committed Apr 29, 2023
1 parent 3f67989 commit 9256822
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 20 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ readme = "README.md"

[tool.poetry.dependencies]
python = "^3.11"
requests = "^2.29.0"


[tool.poetry.group.dev.dependencies]
Expand Down
2 changes: 1 addition & 1 deletion src/builtins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

@dataclasses.dataclass
class BuiltInCallable(LoxCallable):
_short_name: str
_short_name: str = "built-in"

def __str__(self) -> str:
return f"<Built-in function '{self._short_name}'>"
Expand Down
41 changes: 41 additions & 0 deletions src/builtins/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from src.exceptions import PyLoxTypeError
from src.internals.array import LoxArray
from src.internals.hash import LoxHash
from src.internals.http import LoxRequest
from src.internals.string import LoxString

from . import BuiltInCallable
Expand Down Expand Up @@ -136,3 +138,42 @@ def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> flo
return float(arguments[0])
except TypeError:
raise PyLoxTypeError("Argument must be a string or an array.")


@dataclasses.dataclass
class Type(BuiltInCallable):
_short_name = "type"

@property
def arity(self) -> int:
return 1

def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> str:
return type(arguments[0]).__name__


@dataclasses.dataclass
class Request(BuiltInCallable):
_short_name = "requests"
_setup = True

@property
def arity(self) -> int:
return 0

def __call__(
self, interpreter: t.Optional["Interpreter"] = None, arguments: t.Optional[list[str]] = None, /
) -> LoxRequest:
return LoxRequest()


@dataclasses.dataclass
class Hash(BuiltInCallable):
_short_name = "hash"

@property
def arity(self) -> int:
return 0

def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> LoxHash:
return LoxHash()
14 changes: 8 additions & 6 deletions src/headers/generics.lox
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
fun map (callable, arr) {
var length_ = len(arr);
var buffer = array();
for (var i = 0; i < length_; i = i + 1) {
buffer.append(callable(arr.index(i)));
class generics {
map (callable, arr) {
var length_ = len(arr);
var buffer = array();
for (var i = 0; i < length_; i = i + 1) {
buffer.append(callable(arr.get(i)));
}
return buffer;
}
return buffer;
}
82 changes: 82 additions & 0 deletions src/internals/hash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import dataclasses
import typing as t

from src.exceptions import PyLoxAttributeError
from src.utils.expr import Literal

from .callables import LoxCallable
from .types import LoxContainer

if t.TYPE_CHECKING:
from src.interpreter.interpreter import Interpreter
from src.lexer.tokens import Token


__all__: tuple[str, ...] = ("LoxHash",)

# pyright: reportIncompatibleVariableOverride=false


@dataclasses.dataclass
class HashCallable(LoxCallable):
parent: "LoxHash"
token: "Token"

@property
def arity(self) -> int:
raise NotImplementedError

def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> t.Any:
raise NotImplementedError


@dataclasses.dataclass
class Get(HashCallable):
parent: "LoxHash"
token: "Token"

@property
def arity(self) -> int:
return 1

def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> t.Any:
return self.parent.fields.get(arguments[0], Literal(None))


@dataclasses.dataclass
class Set(HashCallable):
parent: "LoxHash"
token: "Token"

@property
def arity(self) -> int:
return 2

def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> t.Any:
self.parent.fields[arguments[0]] = arguments[1]
return Literal(None)


class LoxHash(LoxContainer):
parent: t.Type["LoxHash"]
fields: dict[t.Any, t.Any]
methods: dict[str, t.Type[HashCallable]] = {"get": Get, "set": Set}

def __init__(self) -> None:
self.parent = LoxHash
self.fields = {}

def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.fields})"

def __str__(self) -> str:
return str(self.fields)

def __len__(self) -> int:
return len(self.fields)

def get(self, name: "Token", /) -> t.Any:
try:
return self.methods[name.lexeme](self, name) # type: ignore
except KeyError:
raise PyLoxAttributeError(f"Undefined property '{name.lexeme}'.")
79 changes: 79 additions & 0 deletions src/internals/http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import dataclasses
import typing as t

import requests

from src.exceptions import PyLoxAttributeError, PyLoxRuntimeError

from .callables import LoxCallable
from .types import LoxContainer

if t.TYPE_CHECKING:
from src.interpreter.interpreter import Interpreter
from src.lexer.tokens import Token


__all__: tuple[str, ...] = ("LoxRequest",)


# pyright: reportIncompatibleVariableOverride=false


@dataclasses.dataclass
class RequestCallable(LoxCallable):
parent: "LoxRequest"
token: "Token"

@property
def arity(self) -> int:
raise NotImplementedError

def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> t.Any:
raise NotImplementedError


class LoxRequestModel(LoxContainer):
parent: t.Type["LoxRequestModel"]

def __init__(self, fields: requests.models.Response, /) -> None:
self.parent = LoxRequestModel
try:
self.fields = fields.json()
except ValueError:
self.fields = {}
for key in dir(fields):
if not key.startswith("_") and not callable(getattr(fields, key)):
self.fields[key] = getattr(fields, key)


@dataclasses.dataclass
class Get(RequestCallable):
parent: "LoxRequest"
token: "Token"

@property
def arity(self) -> int:
return 1

def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> LoxRequestModel:
try:
return LoxRequestModel(requests.get(str(arguments[0])))
except requests.exceptions.RequestException as e:
raise PyLoxRuntimeError(interpreter.error(self.token, f"Request failed: {e}"))


class LoxRequest(LoxContainer):
parent: t.Type["LoxRequest"]
fields: t.Optional[dict[str, t.Any]] = None
_methods: dict[str, t.Type[RequestCallable]] = {
"get": Get,
}

def __init__(self, /) -> None:
self.parent = LoxRequest

def get(self, name: "Token", /) -> t.Any:
try:
return self._methods[name.lexeme](self, name) # type: ignore
except KeyError:
raise PyLoxAttributeError(f"Attribute {name.lexeme} not found.")
28 changes: 25 additions & 3 deletions src/internals/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def arity(self) -> int:
return 2

def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> str:
self.parent.fields = self.parent.fields.replace(arguments[0], arguments[1])
self.parent.fields = self.parent.fields.replace(str(arguments[0]), str(arguments[1]))
return self.parent.fields


Expand All @@ -81,7 +81,7 @@ def arity(self) -> int:
return 1

def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> LoxArray:
return LoxArray([LoxString(s) for s in self.parent.fields.split(arguments[0])])
return LoxArray([LoxString(s) for s in self.parent.fields.split(str(arguments[0]))])


@dataclasses.dataclass
Expand All @@ -100,6 +100,19 @@ def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> boo
return check()


@dataclasses.dataclass
class Contains(StringCallable):
parent: "LoxString"
token: "Token"

@property
def arity(self) -> int:
return 1

def __call__(self, interpreter: "Interpreter", arguments: list[t.Any], /) -> bool:
return str(arguments[0]) in str(self.parent.fields)


class LoxString(LoxContainer):
parent: t.Type["LoxString"]
fields: str
Expand All @@ -120,10 +133,11 @@ class LoxString(LoxContainer):
"istitle": Check,
"isupper": Check,
"isascii": Check,
"contains": Contains,
}

def __init__(self, fields: str, /) -> None:
self.fields = fields
self.fields = str(fields)
self.parent = LoxString

def __str__(self) -> str:
Expand All @@ -147,6 +161,14 @@ def __len__(self) -> int:
def __getitem__(self, index: int, /) -> str:
return self.fields[index]

def __hash__(self) -> int:
return hash(self.fields)

def __eq__(self, other: t.Any) -> bool:
if isinstance(other, LoxString):
return self.fields == other.fields
return False

def get(self, name: "Token", /) -> t.Any:
try:
return super().get(name)
Expand Down
6 changes: 5 additions & 1 deletion src/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ def _load_builtins(self) -> None:
methods = inspect.getmembers(__import__(f"src.builtins.{file.stem}", fromlist=["*"]), inspect.isclass)
for _, method in methods:
if issubclass(method, BuiltInCallable) and method is not BuiltInCallable:
if getattr(method, "_setup", False):
token = Token(KeywordTokenType.VAR, method._short_name, None, 0, 0)
self._environment.define(token, method()()) # type: ignore
continue
token = Token(KeywordTokenType.FUN, method._short_name, None, 0, 0)
self._environment.define(token, method(method._short_name)) # type: ignore
self._environment.define(token, method()) # type: ignore

def error(self, token: "Token", message: str, /) -> str:
"""Raise a runtime error."""
Expand Down
5 changes: 4 additions & 1 deletion src/preprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ def _resolve_imports(self) -> None:
paths.add(path)
self._includes[path.as_posix()] = Import(path, n, match.start(), match.end(), module)
for module in self._includes.values():
self._source = self._source.replace(f"import {module.module}", module.path.read_text())
text = module.path.read_text()
if module.module.startswith("<") and "init" not in text and f"class {module.module[1:-1]}" in text:
text += f"\nvar {module.module[1:-1]} = {module.module[1:-1]}();"
self._source = self._source.replace(f"import {module.module}", text)

@property
def includes(self) -> dict[str, Import]:
Expand Down
14 changes: 6 additions & 8 deletions test.lox
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
print 2 ^ 3;
try {
print 5 /;
} catch (e) {
print "Caught exception:";
print e;
} finally {
print "Finally";
var d = hash();
for (var i = 0; i < 10; i = i + 1) {
d.set(str(i), i);
}
print d;
var z = d.get("0");
print z;

0 comments on commit 9256822

Please sign in to comment.