"""Definition Parser class for AaC, contains a load_definition function that handles loading of definition files."""
from typing import Any, Type
from enum import Enum, auto
from aac.context.language_error import LanguageError
from aac.context.definition import Definition
from aac.context.lexeme import Lexeme
from aac.context.util import get_python_module_name, get_python_class_name
[docs]
class DefinitionParser():
"""Definition Parser class, responsible for loading definition files."""
[docs]
def find_definitions_by_name(self, name: str) -> list[Definition]:
"""
Method to find a definition by name.
Args:
name (str): The name of the definition being searched for.
Returns:
The definition with the given name.
"""
result = []
for definition in self.context.get_definitions():
if definition.name == name:
result.append(definition)
# if we didn't find any definitions in the context, check the parsed definitions
if len(result) == 0:
for definition in self.parsed_definitions:
if definition.name == name:
result.append(definition)
return result
[docs]
def get_location_str(self, lexeme_value: str, lexemes: list[Lexeme]) -> str:
"""
Method to find the file name and line number for a requested Lexeme value.
Args:
lexeme_value (str): The Lexeme to match.
lexemes (list[Lexeme]): A list of definition Lexemes.
Returns:
The file name and line number of the requested Lexeme value.
"""
lexeme = []
for lex in lexemes:
# This is just a way to account for different variations of true and false.
if lexeme_value is True and (lex.value == "True" or lex.value == "true" or lex.value == "yes"):
lexeme.append(lex)
if lexeme_value is False and (lex.value == "False" or lex.value == "false"):
lexeme.append(lex)
# The normal found case. lexeme_value is being cast as a string because all values are stored in lexemes as strings.
elif lex.value == str(lexeme_value):
lexeme.append(lex)
location_str = (
"Unable to identify source and location" # this is the 'not found' case
)
if len(lexeme) == 1: # this is the single match case
source_str = lexeme[0].source
line_str = lexeme[0].location.line + 1
location_str = f"File: {source_str} Line: {line_str}"
elif len(lexeme) > 1: # this is the ambiguous match case
# check to see if location source is the same for all matches
if all(
[
lexeme[0].source == lexeme[i].source
for i in range(1, len(lexeme))
]
):
source_str = lexeme[0].source
location_str = f"File: {source_str} Possible Lines: {', '.join([str(lex.location.line+1) for lex in lexeme])}"
else: # if not, just list each possible location
location_str = "Unable to identify unique location - "
for lex in lexeme:
location_str += (
f"File: {lex.source} Line: {lex.location.line+1} "
)
return location_str
[docs]
def get_inheritance_parents(self, definition: Definition) -> list[Type]:
"""
Looks up the inheritance parent classes for the given definition and returns them as a list of Python classes.
Args:
definition (Definition): The definition whose parent class(es) is being searched for.
Returns:
The parent class(es) as a list of Python classes.
"""
inheritance_parents = []
if "extends" in definition.structure[definition.get_root_key()]:
for parent in definition.structure[definition.get_root_key()][
"extends"
]:
# I need to find the definition referenced by the extends
parent_package = parent["package"]
parent_name = parent["name"]
parent_fully_qualified_name = ""
try:
parent_fully_qualified_name = f"{get_python_module_name(parent_package)}.{get_python_class_name(parent_name)}"
except LanguageError as e:
raise LanguageError(
f"Failed to establish parent fully qualified name from parent_package {parent_package} and parent_name {parent_name}: {e.message}", self.get_location_str(parent_name, definition.lexemes)
)
if (
parent_fully_qualified_name
in self.context.context_instance.fully_qualified_name_to_class
):
inheritance_parents.append(
self.context.context_instance.fully_qualified_name_to_class[
parent_fully_qualified_name
]
)
else:
# there is a chance that processing order just means we haven't gotten to the parent yet
parent_definition = None
if (
parent_fully_qualified_name
in self.fully_qualified_name_to_definition
):
parent_definition = self.fully_qualified_name_to_definition[
parent_fully_qualified_name
]
if not parent_definition:
raise LanguageError(
f"Cannot find parent definition {parent_fully_qualified_name} for {definition.name}",
self.get_location_str(parent_name, definition.lexemes),
)
if parent_definition.get_root_key() == "schema":
inheritance_parents.append(
self.create_schema_class(parent_definition)
)
else:
# AaC only supports schema inheritance
raise LanguageError(
f"AaC extension is only supported for schema. Unable to create parent class with AaC root: {parent_definition.get_root_key()}",
self.get_location_str(parent_name, definition.lexemes),
)
return inheritance_parents
[docs]
def create_enum_class(self, enum_definition: Definition) -> Enum:
"""
Creates an enum class from a given enum definition.
Args:
enum_definition (Definition): An enum definition to convert to a class.
Returns:
The created class.
"""
if enum_definition.get_root_key() != "enum":
raise LanguageError(
f"Definition {enum_definition.name} is not an enum",
self.get_location_str(
enum_definition.get_root_key(), enum_definition.lexemes
)
)
try:
fully_qualified_name = enum_definition.get_fully_qualified_name()
except LanguageError as e:
raise LanguageError(
f"Failed to create fully qualified name for definition {enum_definition.name}: {e.message}",
self.get_location_str(enum_definition.name, enum_definition.lexemes),
)
if (
fully_qualified_name
in self.context.context_instance.fully_qualified_name_to_class
):
# we've already created the class, so nothing to do here
return self.context.context_instance.fully_qualified_name_to_class[
fully_qualified_name
]
# Note: trying to allow extension with Enums fails, so I just removed it
# but we can revisit and try to find a solution in the future if needed
values = {}
if "values" in enum_definition.structure["enum"]:
for value in enum_definition.structure["enum"]["values"]:
values[value] = auto()
# create the enum class
instance_class = None
try:
instance_class = Enum(
enum_definition.get_python_class_name(),
values,
module=enum_definition.get_python_module_name(),
)
except LanguageError as e:
raise LanguageError(
f"Failed to create Enum instance_class for {enum_definition.name}: {e.message}",
self.get_location_str(enum_definition.name, enum_definition.lexemes),
)
self.context.context_instance.fully_qualified_name_to_class[
fully_qualified_name
] = instance_class
return instance_class
[docs]
def add_field_to_class(self, field: dict, instance_class: Type, schema_definition: Definition) -> Type:
"""
Creates an attribute within the instance class and adds the field name and type to it.
Args:
field (dict): The field being loaded into the instance class.
instance_class (Type): The instance class being loaded with field definitions.
schema_definition (Definition): The schema definition containing the fields being iterated through and loaded.
Returns:
the instance class after being loaded with field definitions.
"""
field_name = field["name"]
field_type = field["type"]
is_list = False
clean_field_type = field_type
if field_type.endswith("[]"):
is_list = True
clean_field_type = field_type[:-2]
if "(" in clean_field_type:
clean_field_type = clean_field_type[: clean_field_type.find("(")]
# let's make sure the type of the field is known, or create it if it's not
potential_definitions = self.find_definitions_by_name(clean_field_type)
if len(potential_definitions) != 1:
if len(potential_definitions) == 0:
raise LanguageError(
f"Could not find AaC definition for type {clean_field_type} while loading {schema_definition.name}",
self.get_location_str(field_type, schema_definition.lexemes),
)
else:
raise LanguageError(
f"Discovered multiple AaC definitions for type {clean_field_type} while loading {schema_definition.name}. You may need to add a package name to differentiate.",
self.get_location_str(field_type, schema_definition.lexemes),
)
parsed_definition = potential_definitions[0]
self.create_schema_class(parsed_definition)
# since python is dynamically typed, we really don't have to worry about setting a type when we create the field
# we just need to make sure a reasonable default value is used, so for us that means an empty list or None
# Question: is there ever a case where we may need a dict value?
if is_list:
setattr(instance_class, field_name, [])
else:
setattr(instance_class, field_name, None)
return instance_class
[docs]
def create_instance_class(self, inheritance_parents: list, schema_definition: Definition) -> Type:
"""
Method to create an instance class type for the given definition.
Args:
inheritance_parents (list[Type]): Types that this definition inherits from.
schema_definition (Definition): The definition being converted to an instance class.
Returns:
The created instance class.
"""
if len(inheritance_parents) == 0:
base_content = [object]
else:
base_content = inheritance_parents
try:
instance_class = type(
schema_definition.get_python_class_name(),
tuple(base_content),
{"__module__": schema_definition.get_python_module_name()}
)
except LanguageError as e:
raise LanguageError(
f"Failed to create instance_class for {schema_definition.name}: {e.message}",
self.get_location_str(
schema_definition.name, schema_definition.lexemes
)
)
return instance_class
[docs]
def create_schema_class(self, schema_definition: Definition) -> Type:
"""
Creates a schema class from a given schema definition.
Args:
schema_definition (Definition): A schema definition to convert to a class.
Returns:
The created class.
"""
try:
fully_qualified_name = schema_definition.get_fully_qualified_name()
except LanguageError as e:
raise LanguageError(
f"Failed to create fully qualified name for definition {schema_definition.name}: {e.message}",
self.get_location_str(schema_definition.name, schema_definition.lexemes),
)
if schema_definition.get_root_key() == "primitive":
# this is a primitive, so there's no structure to create, just return the python type
return eval(self.primitive_name_to_py_type[schema_definition.name])
elif schema_definition.get_root_key() == "enum":
# this is an enum, so create the enum class
return self.create_enum_class(schema_definition)
elif schema_definition.get_root_key() != "schema":
raise LanguageError(
f"Definition {schema_definition.name} is not a schema",
self.get_location_str(
schema_definition.get_root_key(), schema_definition.lexemes
)
)
if (
fully_qualified_name
in self.context.context_instance.fully_qualified_name_to_class
):
# we've already created the class, so nothing to do here
return self.context.context_instance.fully_qualified_name_to_class[
fully_qualified_name
]
inheritance_parents = self.get_inheritance_parents(schema_definition)
instance_class = self.create_instance_class(inheritance_parents, schema_definition)
# now add the fields to the class
for field in schema_definition.structure["schema"]["fields"]:
instance_class = self.add_field_to_class(field, instance_class, schema_definition)
# finally store the class in the context
self.context.context_instance.fully_qualified_name_to_class[
fully_qualified_name
] = instance_class
return instance_class
[docs]
def create_object_instance(self, type_class: Type, fields: dict) -> Any:
"""
Creates an instance object from the given fields.
Args:
type_class (Type): The class created from the field type.
fields (dict): Given fields to create instances from.
Returns:
The created instance object.
"""
result = type_class()
for field_name, field_value in fields.items():
setattr(result, field_name, field_value)
return result
[docs]
def get_defined_fields(self, package: str, name: str) -> list[str]:
"""
Returns a list of defined fields for the given definition.
Args:
package (str): The definition package.
name (str): The definition name.
Returns:
A list of definition fields.
"""
result = []
defining_definition = None
for definition in self.context.get_definitions() + self.parsed_definitions:
if definition.name == name and definition.package == package:
defining_definition = definition
break
if (
"extends"
in defining_definition.structure[defining_definition.get_root_key()]
):
for parent in defining_definition.structure[
defining_definition.get_root_key()
]["extends"]:
result.extend(self.get_defined_fields(parent["package"], parent["name"]))
if "fields" in defining_definition.structure[definition.get_root_key()]:
result.extend(
[
field["name"]
for field in definition.structure[definition.get_root_key()][
"fields"
]
]
)
return result
[docs]
def populate_sub_fields(self, subfields: dict, defining_definition: Definition, item: dict, lexemes: list[Lexeme], definition: Definition) -> dict:
"""
Method used to populate sub-fields in an instance class field.
Args:
subfields (dict): dictionary containing relevant sub-fields.
defining_definition (Definition): The definition of the field type.
item (dict): The current field definition which contains sub-fields.
lexemes (List[Lexeme]): A list of definition Lexemes.
definition (Definition): The overall definition containing the field.
Returns:
The populated dictionary of sub-fields.
"""
for subfield in defining_definition.structure["schema"][
"fields"
]:
subfield_name = subfield["name"]
subfield_type = subfield["type"]
subfield_is_required = False
if "is_required" in subfield:
subfield_is_required = subfield["is_required"]
subfield_value = None
if subfield_name in item:
subfield_value = item[subfield_name]
else:
if "default" in subfield:
# let's see if we need to cast the value
subfield_default_str = subfield["default"]
if isinstance(subfield_default_str, str):
type_map = {
"int": int,
"number": float,
"bool": lambda x: x.lower()
in ("yes", "true", "t", "1"),
"string": str,
}
if subfield_type in type_map:
subfield_value = type_map[
subfield_type
](subfield_default_str)
else:
subfield_value = subfield_default_str
# we need to eliminate previously covered lexemes, so go through the list until we find subfield_name then add it and everything else
found = False
sub_lexemes = []
for lex in lexemes:
if lex.value == subfield_name:
found = True
sub_lexemes.append(lex)
if found:
sub_lexemes.append(lex)
subfields[subfield_name] = self.create_field_instance(
subfield_name,
subfield_type,
subfield_is_required,
subfield_value,
sub_lexemes,
definition,
)
return subfields
[docs]
def field_instance_creator_list(self, lexemes: list, field_value: Any, field_name: str, defining_definition: Definition, instance: list, instance_class: Type, definition: Definition) -> list:
"""
Method to create instance fields from list type fields.
Args:
lexemes (List[Lexeme]): A list of definition Lexemes.
field_value (Any): Value stored in the field.
field_name (str): The name of the field.
defining_definition (Definition): Definition of the field type.
instance (list[Any]: The instance that will contain the created field)
instance_class (Type): The instance type class
definition (Definition): Definition containing the field being checked.
Returns:
The updated instance containing the created field.
"""
for item in field_value:
if not isinstance(item, dict):
raise LanguageError(
f"Invalid parsed value for field '{field_name}'. Expected type 'dict', but found '{type(item)}'. Value = {item}",
self.get_location_str(field_name, lexemes),
)
# go through the fields and create instances for each
subfields = {}
if "fields" not in defining_definition.structure["schema"]:
raise LanguageError(
f"Schema '{defining_definition.name}' does not contain any fields.",
self.get_location_str(field_name, lexemes),
)
# make sure there are no undefined fields
defined_field_names = self.get_defined_fields(
defining_definition.package, defining_definition.name
)
item_field_names = [field for field in item.keys()]
for item_field_name in item_field_names:
if item_field_name not in defined_field_names:
raise LanguageError(
f"Found undefined field name '{item_field_name}' when expecting {defined_field_names} as defined in {defining_definition.name}",
self.get_location_str(item_field_name, lexemes),
)
subfields = self.populate_sub_fields(subfields, defining_definition, item, lexemes, definition)
instance.append(
self.create_object_instance(instance_class, subfields)
)
return instance
[docs]
def field_instance_creator_not_list(self, lexemes: list, field_value: Any, field_name: str, defining_definition: Definition, instance: list, instance_class: Type, definition: Definition) -> list:
"""
Method to create instance fields from non-list type fields.
Args:
lexemes (List[Lexeme]): A list of definition Lexemes.
field_value (Any): Value stored in the field.
field_name (str): The name of the field.
defining_definition (Definition): Definition of the field type.
instance (list[Any]): The instance that will contain the created field
instance_class (Type): The instance type class
definition (Definition): Definition containing the field being checked.
Returns:
The updated instance containing the created field
"""
defined_field_names = self.get_defined_fields(
defining_definition.package, defining_definition.name
)
item_field_names = [field for field in field_value.keys()]
for item_field_name in item_field_names:
if item_field_name not in defined_field_names:
raise LanguageError(
f"Found undefined field name '{item_field_name}' when expecting {defined_field_names} as defined in {defining_definition.name}",
self.get_location_str(item_field_name, lexemes),
)
subfields = {}
if "fields" not in defining_definition.structure["schema"]:
raise LanguageError(
f"Schema '{defining_definition.name}' does not contain any fields.",
self.get_location_str(field_name, lexemes),
)
subfields = self.populate_sub_fields(subfields, defining_definition, field_value, lexemes, definition)
instance = self.create_object_instance(instance_class, subfields)
return instance
[docs]
def field_instance_check(self, is_list: bool, field_value: Any, field_name: str, is_required: bool, lexemes: list[Lexeme], defining_definition: Definition, instance_class: Type, definition: Definition) -> list:
"""
Method used to check if a field is a list, and to call the corresponding field instance creator method.
Args:
is_list (bool): Boolean value determining if the field type is a list.
field_value (Any): Value stored in the field.
field_name (str): The name of the field.
lexemes (List[Lexeme]): A list of definition Lexemes.
defining_definition (Definition): Definition of the field type.
instance_class (Type): The class type of the instance being created.
definition (Definition): Definition containing the field being checked.
Returns:
The created instance list.
"""
if is_list:
instance = []
if not field_value:
if is_required:
raise LanguageError(
message=f"Missing required field {field_name}",
location=self.get_location_str(definition.name, lexemes)
)
else:
if not isinstance(field_value, list):
if is_required:
raise LanguageError(
f"Invalid parsed value for field '{field_name}'. Expected type 'list', but found '{type(field_value)}'. Value = {field_value}",
self.get_location_str(field_name, lexemes),
)
else:
return instance
instance = self.field_instance_creator_list(lexemes, field_value, field_name, defining_definition, instance, instance_class, definition)
else:
instance = None
if not field_value:
if is_required:
raise LanguageError(
f"Missing required field {field_name}",
self.get_location_str(field_name, lexemes),
)
else:
return instance
if not isinstance(field_value, dict):
# this is a complex type defined by a schema, with a field value so it should be a dict
raise LanguageError(
f"Invalid parsed value for field '{field_name}'. Expected type 'dict', but found '{type(field_value)}'. Value = {field_value}",
self.get_location_str(field_name, lexemes),
)
# make sure there are no undefined fields
instance = self.field_instance_creator_not_list(lexemes, field_value, field_name, defining_definition, instance, instance_class, definition)
return instance
[docs]
def primitive_field_value_check(self, is_list: bool, field_value: Any, field_name: str, is_required: bool, lexemes: list, defining_definition: Definition, definition: Definition) -> Any:
"""
Method used to ensure primitive type field definitions have valid values.
Args:
is_list (bool): Boolean value determining if the field type is a list.
field_value (Any): Value stored in the field.
field_name (str): The name of the field.
is_required (bool): Boolean value determining if the field is required.
lexemes (List[Lexeme]): A list of definition Lexemes.
defining_definition (Definition): Definition defining the field type.
definition (Definition): Definition containing the field being checked.
Returns:
The content of the primitive type field value.
"""
python_type = defining_definition.structure["primitive"]["python_type"]
if not field_value:
if is_required:
raise LanguageError(message=f"Missing required field {field_name}.", location=self.get_location_str(definition.name, lexemes))
if is_list:
field_value = []
elif is_list:
for item in field_value:
if not isinstance(item, eval(python_type)):
raise LanguageError(
message=f"Invalid value for field '{field_name}'. Expected type '{python_type}', but found '{type(item)}'",
location=self.get_location_str(field_value, lexemes),
)
else:
if "Any" != python_type:
if not isinstance(field_value, eval(python_type)):
raise LanguageError(
f"Invalid value for field '{field_name}'. Expected type '{python_type}', but found '{type(field_value)}'",
self.get_location_str(field_value, lexemes),
)
return field_value
[docs]
def enum_field_list_check(self, field_value: Any, field_name: str, lexemes: list, defining_definition: Definition, enum_class: str) -> list:
"""
Method used to ensure each item in a list of enum fields have valid values.
Args:
field_value (Any): Value stored in the field.
field_name (str): The name of the field.
lexemes (List[Lexeme]): A list of definition Lexemes.
defining_definition (Definition): Definition containing the field being checked.
enum_class (str): The name of the enums defining definition
Returns:
The list of valid enum field values.
"""
result = []
for item in field_value:
if not isinstance(item, str):
raise LanguageError(
f"Invalid value for field '{field_name}'. Expected type 'str', but found '{type(item)}'",
self.get_location_str(field_name, lexemes),
)
try:
result.append(getattr(enum_class, item))
except ValueError:
raise LanguageError(
f"{item} is not a valid value for enum {defining_definition.name}",
self.get_location_str(item, lexemes),
)
return result
[docs]
def enum_field_value_check(self, is_list: bool, field_value: Any, field_name: str, lexemes: list, defining_definition: Definition) -> list:
"""
Method used to ensure enum type field definitions have valid values.
Args:
is_list (bool): Boolean value determining if the field type is a list.
field_value (Any): Value stored in the field.
field_name (str): The name of the field.
lexemes (List[Lexeme]): A list of definition Lexemes.
defining_definition (Definition): Definition containing the field being checked.
Returns:
The list of valid enum field values.
"""
try:
enum_class = self.context.context_instance.fully_qualified_name_to_class[
defining_definition.get_fully_qualified_name()
]
except LanguageError as e:
raise LanguageError(
f"Failed to create Enum instance_class for {defining_definition.name}: {e.message}",
self.get_location_str(defining_definition.name, defining_definition.lexemes),
)
if not enum_class:
enum_class = self.create_enum_class(defining_definition)
if is_list:
return self.enum_field_list_check(field_value, field_name, lexemes, defining_definition, enum_class)
else:
if not field_value:
return [None]
try:
return self.context.create_aac_enum(
defining_definition.get_fully_qualified_name(), field_value
)
except ValueError:
raise LanguageError(
f"{field_value} is not a valid value for enum {defining_definition.name}",
self.get_location_str(field_value, lexemes),
)
[docs]
def schema_field_value_check(self, is_list: bool, field_value: Any, field_name: str, is_required: bool, lexemes: list, defining_definition: Definition, definition: Definition) -> list:
"""
Method used to ensure schema type field definitions have valid values.
Args:
is_list (bool): Boolean value determining if the field type is a list.
field_value (Any): Value stored in the field.
field_name (str): The name of the field.
lexemes (List[Lexeme]): A list of definition Lexemes.
defining_definition (Definition): Definition of the field type.
definition (Definition): Definition containing the field being checked.
Returns:
The List of valid schema defined field values.
"""
try:
field_fully_qualified_name = (
defining_definition.get_fully_qualified_name()
)
except LanguageError as e:
raise LanguageError(
f"Failed to create fully qualified name for definition {defining_definition.name}: {e.message}",
self.get_location_str(defining_definition.name, defining_definition.lexemes),
)
instance_class = self.context.context_instance.fully_qualified_name_to_class[
field_fully_qualified_name
]
if not instance_class:
if defining_definition.get_root_key() == "schema":
instance_class = self.create_schema_class(defining_definition)
else:
raise LanguageError(
f"Unable to process AaC definition of type {field_fully_qualified_name} with root {defining_definition.get_root_key()}",
self.get_location_str(field_name, lexemes),
)
instance = self.field_instance_check(is_list, field_value, field_name, is_required, lexemes, defining_definition, instance_class, definition)
return instance
[docs]
def create_field_instance(
self,
field_name: str,
field_type: str,
is_required: bool,
field_value: Any,
lexemes: list[Lexeme],
definition: Definition
) -> Any:
"""
Adds an entry to the instance attribute of a definition for the given field.
Args:
field_name (str): Name of the field.
field_type (str): Type of the field.
is_required (bool): Contents of the is_required field for the specified field.
field_value (Any): The value for the specified field.
lexemes (list[Lexeme]): A list of definition Lexemes.
definition (Definition): Definition containing the field instance being created
Returns:
The instance field value.
"""
is_list = False
clean_field_type = field_type
if field_type.endswith("[]"):
is_list = True
clean_field_type = field_type[:-2]
if "(" in clean_field_type:
clean_field_type = clean_field_type[: clean_field_type.find("(")]
# now get the defining definition from the clean_field_type
defining_definitions = self.find_definitions_by_name(clean_field_type)
if not defining_definitions or len(defining_definitions) == 0:
raise LanguageError(
f"Could not find definition for '{clean_field_type}'.",
self.get_location_str(field_type, lexemes),
)
elif len(defining_definitions) > 1:
raise LanguageError(
f"Found multiple definitions for '{clean_field_type}'.",
self.get_location_str(field_type, lexemes),
)
defining_definition = defining_definitions[0]
if defining_definition.get_root_key() == "primitive":
# this is a primitive, so ensure the parsed value aligns with the type and return it
return self.primitive_field_value_check(is_list, field_value, field_name, is_required, lexemes, defining_definition, definition)
elif defining_definition.get_root_key() == "enum":
# this is an enum, so ensure the parsed value aligns with the type and return it
return self.enum_field_value_check(is_list, field_value, field_name, lexemes, defining_definition)
else: # this isn't a primitive and isn't an enum, so it must be a schema
return self.schema_field_value_check(is_list, field_value, field_name, is_required, lexemes, defining_definition, definition)
[docs]
def create_definition_instance(self, definition: Definition) -> Any:
"""
Populates the instance field of a given definition.
Args:
definition (Definition): The given definition being populated.
Returns:
The populated instance for the given definition.
"""
instance = None
defining_definition = None
for item in self.context.get_definitions() + self.parsed_definitions:
if item.get_root_key() == "schema":
if "root" in item.structure["schema"]:
if (
definition.get_root_key()
== item.structure["schema"]["root"]
):
defining_definition = item
if not defining_definition:
raise LanguageError(
f"Could not find definition for {definition.name} with root {definition.get_root_key()}",
self.get_location_str(definition.get_root_key(), definition.lexemes),
)
if defining_definition.get_root_key() == "schema":
# since schemas are how we define "all the things" the root key of the defining definition should be 'schema'
self.create_schema_class(defining_definition)
else:
raise LanguageError(
f"Definition for root key '{defining_definition.get_root_key()}' is not a Schema.",
self.get_location_str(definition.get_root_key(), definition.lexemes),
)
instance = self.create_field_instance(
"root",
defining_definition.name,
True,
definition.structure[definition.get_root_key()],
definition.lexemes,
definition,
)
definition.instance = instance
return instance
[docs]
def set_qualified_name(self, definition: Definition):
"""
Method to set the fully qualified name for a definition.
Args:
definition (Definition): The definition whose fully qualified name will be set.
"""
if definition.get_root_key() == "primitive":
self.primitive_name_to_py_type[definition.name] = definition.structure["primitive"]["python_type"]
if "package" in definition.structure[definition.get_root_key()]:
try:
fully_qualified_name = definition.get_fully_qualified_name()
except LanguageError as e:
raise LanguageError(
f"Failed to create fully qualified name for the definition {definition.name}: {e.message}",
self.get_location_str(definition.name, definition.lexemes),
)
# This is so requirements specifically do not get overwritten. Although other definition types may still get overwritten, so we may need to find a better solution eventually.
if definition.get_root_key() == "req":
req_id = definition.structure["req"]["id"]
fully_qualified_name = f"{fully_qualified_name}_{req_id}"
self.fully_qualified_name_to_definition[fully_qualified_name] = definition
# Start the load_definition function code here
[docs]
def load_definitions(
self, context, parsed_definitions: list[Definition]
) -> list[Definition]:
"""
Loads the given definitions into the context and populates the instance with a python object.
Args:
context (LanguageContext): An instance of the active LanguageContext.
parsed_definitions: (list[Definition]): The parsed contents of a definition file.
Returns:
The parsed definitions to load into the LanguageContext.
"""
# Maintainer note: Yes, this function is a bit of a monster...sorry about that.
# I wanted to keep all this stuff together because if it all works out this should be the
# only place where we have to deal with navigating the structure of the definitions and
# not using the python objects. In order for this to work, any changes in here should
# avoid the use of he definition instance...other than actually creating it.
schema_defs_by_root = {}
self.context = context
self.parsed_definitions = parsed_definitions
self.primitive_name_to_py_type = {}
self.fully_qualified_name_to_definition = {}
for definition in self.parsed_definitions + self.context.get_definitions():
self.set_qualified_name(definition)
for definition in self.context.get_definitions():
if definition.get_root_key() == "schema":
if definition.instance.root:
schema_defs_by_root[definition.instance.root] = definition
result = []
for definition in self.parsed_definitions:
# create and register the instance
self.create_definition_instance(definition)
definition.source.is_loaded_in_context = True
result.append(definition)
self.context.context_instance.definitions.add(definition)
fully_qualified_name = f"{definition.package}.{definition.name}"
# This is so requirements specifically do not get overwritten. Although other definition types may still get overwritten, so we may need to find a better solution eventually.
if definition.get_root_key() == "req":
req_id = definition.structure["req"]["id"]
fully_qualified_name = f"{fully_qualified_name}_{req_id}"
self.context.context_instance.fully_qualified_name_to_definition[
fully_qualified_name
] = definition
return result