Skip to content

Commit

Permalink
chore: fix document accesor
Browse files Browse the repository at this point in the history
  • Loading branch information
lumasepa committed Nov 26, 2024
1 parent 4358b7f commit e721a9d
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 98 deletions.
21 changes: 0 additions & 21 deletions doc.json

This file was deleted.

21 changes: 21 additions & 0 deletions nada_dsl/ast_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,24 @@ def to_mir(self):
"source_ref_index": self.source_ref.to_index(),
}
}

@dataclass
class DocumentAccessorASTOperation(ASTOperation):
"""AST representation of an document accessor operation."""

key: str
source: int

def child_operations(self):
return [self.source]

def to_mir(self):
return {
"DocumentAccessor": {
"id": self.id,
"key": self.key,
"source": self.source,
"type": self.ty,
"source_ref_index": self.source_ref.to_index(),
}
}
2 changes: 2 additions & 0 deletions nada_dsl/compiler_frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import List, Dict, Any, Optional, Tuple
from sortedcontainers import SortedDict

from nada_dsl import DocumentAccessorASTOperation
from nada_dsl.ast_util import (
AST_OPERATIONS,
ASTOperation,
Expand Down Expand Up @@ -300,6 +301,7 @@ def process_operation(
NadaFunctionArgASTOperation,
NTupleAccessorASTOperation,
ObjectAccessorASTOperation,
DocumentAccessorASTOperation,
),
):
processed_operation = ProcessOperationOutput(operation.to_mir(), None)
Expand Down
3 changes: 0 additions & 3 deletions nada_dsl/nada_types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ def is_numeric(self) -> bool:


# TODO: abstract?
@dataclass
class DslType:
"""Nada type class.
Expand All @@ -136,8 +135,6 @@ class DslType:
"""

child: OperationType

def __init__(self, child: OperationType):
"""NadaType default constructor
Expand Down
176 changes: 104 additions & 72 deletions nada_dsl/nada_types/collections.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Nada Collection type definitions."""

import os
from dataclasses import dataclass
import inspect
import json
Expand All @@ -14,7 +14,7 @@
NewASTOperation,
ObjectAccessorASTOperation,
ReduceASTOperation,
UnaryASTOperation,
UnaryASTOperation, DocumentAccessorASTOperation,
)
from nada_dsl.nada_types import DslType

Expand Down Expand Up @@ -169,7 +169,7 @@ def type(self):
return TupleType(self.left_type, self.right_type)


def _generate_accessor(ty: Any, accessor: Any) -> DslType:
def _wrap_accessor_with_type(ty: Any, accessor: Any) -> DslType:
if hasattr(ty, "ty") and ty.ty.is_literal(): # TODO: fix
raise TypeError("Literals are not supported in accessors")
return ty.instantiate(accessor)
Expand All @@ -180,7 +180,7 @@ class NTupleType(NadaType):

is_compound = True

def __init__(self, types: List[DslType]):
def __init__(self, types: List[NadaType]):
self.types = types

def instantiate(self, child_or_value):
Expand Down Expand Up @@ -228,7 +228,7 @@ def __getitem__(self, index: int) -> DslType:
source_ref=SourceRef.back_frame(),
)

return _generate_accessor(self.types[index], accessor)
return _wrap_accessor_with_type(self.types[index], accessor)

def type(self):
"""Metatype for NTuple"""
Expand Down Expand Up @@ -270,7 +270,7 @@ class ObjectType(NadaType):

is_compound = True

def __init__(self, types: Dict[str, DslType]):
def __init__(self, types: Dict[str, NadaType]):
self.types = types

def to_mir(self):
Expand Down Expand Up @@ -318,7 +318,7 @@ def __getattr__(self, attr: str) -> DslType:
source_ref=SourceRef.back_frame(),
)

return _generate_accessor(self.types[attr], accessor)
return _wrap_accessor_with_type(self.types[attr], accessor)

def type(self):
"""Metatype for Object"""
Expand Down Expand Up @@ -354,85 +354,117 @@ def store_in_ast(self, ty: object):
ty=ty,
)

class DocumentType(NadaType):
"""Marker type for Objects."""

def _process_schema(schema_node):
if "type" not in schema_node:
raise TypeError("Missing 'type' in schema node")

match schema_node["type"]:
case "integer":
return PublicInteger(child=None)
case "boolean":
return PublicBoolean(child=None)
case "array":
items_schema = schema_node.get("items")
if items_schema is None:
raise TypeError("Array schema missing 'items'")
ntuple = NTuple.__new__(NTuple)
ntuple.types = []
ntuple.child = None
for index, item_schema in enumerate(items_schema):
ty = _process_schema(item_schema)
ntuple.types.append(ty)
return ntuple
case "object":
properties = schema_node.get("properties", {})
obj = Object.__new__(Object)
obj.types = {}
obj.child = None
for key, prop_schema in properties.items():
ty = _process_schema(prop_schema)
obj.types[key] = ty
return obj
case _:
raise TypeError(f"Unsupported type in schema: {schema_node['type']}")


def _assign_accessors(parent):
if isinstance(parent, NTuple):
for index, ty in enumerate(parent.types):
accessor = NTupleAccessor(
index=index,
child=parent,
source_ref=SourceRef.back_frame(),
)
ty.child = accessor
_assign_accessors(ty)
elif isinstance(parent, Object):
for key, ty in parent.types.items():
accessor = ObjectAccessor(
key=key,
child=parent,
source_ref=SourceRef.back_frame(),
)
ty.child = accessor
_assign_accessors(ty)
else:
# Base case: PublicInteger or PublicBoolean
pass
is_compound = True

def __init__(self, types: Dict[str, NadaType]):
self.types = types

def to_mir(self):
"""Convert an object into a Nada type."""
return {
"Document": {"types": {name: ty.to_mir() for name, ty in self.types.items()}}
}

def instantiate(self, child_or_value):
return Document(child_or_value, self.types)

class Document(Object):
class Document(DslType):
"""The Document type"""

def __init__(self, child, filepath: str):
with open(filepath, "r") as schema_file:
schema = json.load(schema_file)
def __init__(self, child, filepath: str = None, public: bool = False, schema: dict = None):
if not schema:
with open(filepath, "r") as schema_file:
schema = json.load(schema_file)

try:
Draft7Validator.check_schema(schema)
except SchemaError as e:
raise TypeError("Schema validation error:", e.message)

if schema["type"] != "object":
raise TypeError("Only objects are supported at the root")

# result = _process_schema(self, 0, 0, schema)
# result = _process_schema(schema)
# _assign_accessors(result)
self.__schema = schema
self.__public = public
super().__init__(child)

def __getattr__(self, item):
if item not in self.__schema["properties"]:
raise AttributeError(
f"'Document has no attribute '{item}'"
)

accessor = DocumentAccessor(
key=item,
child=self,
source_ref=SourceRef.back_frame(),
)

attribute_type = self.__schema_to_nada_type(self.__schema["properties"][item])

return _wrap_accessor_with_type(attribute_type, accessor)

def __schema_to_nada_type(self, schema: dict) -> NadaType:
IntegerType = PublicIntegerType if self.__public else SecretIntegerType
UnsignedIntegerType = PublicUnsignedIntegerType if self.__public else SecretUnsignedIntegerType
BooleanType = PublicBooleanType if self.__public else SecretBooleanType
if schema["type"] == "integer" and "nada_type" in schema and schema["nada_type"] == "unsigned":
return UnsignedIntegerType()
if schema["type"] == "integer":
return IntegerType()
elif schema["type"] == "boolean":
return BooleanType()
elif schema["type"] == "object":
return ObjectType(types={key: self.__schema_to_nada_type(value) for key, value in schema["properties"].items()})
elif schema["type"] == "array" and "prefixItems" in schema:
return NTupleType([self.__schema_to_nada_type(value) for value in schema["prefixItems"]])
elif schema["type"] == "array":
if "size" not in schema:
raise TypeError("size not defined in array schema")
return ArrayType(contained_type=self.__schema_to_nada_type(schema["items"]), size=schema["size"])
else:
raise TypeError(f"type '{schema['type']}' not supported in json schema")

super().__init__(child=child, types={}) # TMP
def type(self):
return DocumentType({key: self.__schema_to_nada_type(value) for key, value in self.__schema["properties"].items()})

class PublicDocument(Document):
def __init__(self, child, filepath: str):
super().__init__(child, filepath, True)

class SecretDocument(Document):
def __init__(self, child, filepath: str):
super().__init__(child, filepath, False)

@dataclass
class DocumentAccessor:
"""Accessor for Object"""

child: Object
key: str
source_ref: SourceRef

def __init__(
self,
child: Object,
key: str,
source_ref: SourceRef,
):
self.id = next_operation_id()
self.child = child
self.key = key
self.source_ref = source_ref

def store_in_ast(self, ty: object):
"""Store this accessor in the AST."""
AST_OPERATIONS[self.id] = DocumentAccessorASTOperation(
id=self.id,
source=self.child.child.id,
key=self.key,
source_ref=self.source_ref,
ty=ty,
)

class Zip:
"""The Zip operation."""
Expand Down
40 changes: 40 additions & 0 deletions test-programs/doc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"a": {
"type": "integer"
},
"b": {
"type": "boolean"
},
"c": {
"type": "object",
"properties": {
"c": {
"type": "integer"
}
}
},
"d": {
"type": "array",
"items": {
"type": "integer"
},
"size": 2
},
"e": {
"type": "array",
"prefixItems": [{"type": "integer"}, {"type": "boolean"}]
},
"f": {
"type": "integer",
"nada_type": "unsigned_integer"
}
},
"required": [
"a",
"b"
],
"additionalProperties": false
}
Loading

0 comments on commit e721a9d

Please sign in to comment.