Skip to content

Commit

Permalink
Adds new model changes for scalar (amazon-ion#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
desaikd committed Nov 1, 2024
1 parent b9c9144 commit f3ae683
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 58 deletions.
4 changes: 4 additions & 0 deletions code-gen-projects/schema/scalar.isl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type::{
name: scalar,
type: string
}
177 changes: 167 additions & 10 deletions src/bin/ion/commands/generate/generator.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::commands::generate::context::CodeGenContext;
use crate::commands::generate::model::{
AbstractDataType, DataModelNode, FieldPresence, FieldReference, FullyQualifiedTypeReference,
StructureBuilder,
ScalarBuilder, StructureBuilder, WrappedScalarBuilder,
};
use crate::commands::generate::result::{
invalid_abstract_data_type_error, invalid_abstract_data_type_raw_error, CodeGenResult,
Expand Down Expand Up @@ -287,6 +287,7 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
type_name,
isl_type,
&mut code_gen_context,
true,
)?;

// add this nested type to parent code gene context's current list of nested types
Expand Down Expand Up @@ -315,6 +316,7 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
isl_type_name,
isl_type,
&mut code_gen_context,
false,
)?;

// add the entire type store and the data model node into tera's context to be used to render template
Expand All @@ -336,6 +338,7 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
isl_type_name: &String,
isl_type: &IslType,
code_gen_context: &mut CodeGenContext,
is_nested_type: bool,
) -> CodeGenResult<DataModelNode> {
self.current_type_fully_qualified_name
.push(isl_type_name.to_case(Case::UpperCamel));
Expand All @@ -348,6 +351,12 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
.any(|it| matches!(it.constraint(), IslConstraintValue::Fields(_, _)))
{
self.build_structure_from_constraints(constraints, code_gen_context, isl_type)?
} else if Self::contains_scalar_constraints(constraints) {
if is_nested_type {
self.build_scalar_from_constraints(constraints, code_gen_context, isl_type)?
} else {
self.build_wrapped_scalar_from_constraints(constraints, code_gen_context, isl_type)?
}
} else {
todo!("Support for sequences, maps, scalars, and tuples not implemented yet.")
};
Expand All @@ -371,6 +380,13 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
Ok(data_model_node)
}

/// Verifies if the given constraints contain a `type` constraint without any container type references. (e.g. `sexp`, `list`, `struct`)
fn contains_scalar_constraints(constraints: &[IslConstraint]) -> bool {
constraints.iter().any(|it| matches!(it.constraint(), IslConstraintValue::Type(isl_type_ref) if isl_type_ref.name().as_str() != "list"
&& isl_type_ref.name().as_str() != "sexp"
&& isl_type_ref.name().as_str() != "struct"))
}

fn render_generated_code(
&mut self,
type_name: &str,
Expand Down Expand Up @@ -447,14 +463,43 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
name
}

/// Build structure from constraints
/// Builds `AbstractDataType::Structure` from the given constraints.
/// e.g. for a given type definition as below:
/// ```
/// type::{
/// name: Foo,
/// type: struct,
/// fields: {
/// a: string,
/// b: int,
/// }
/// }
/// ```
/// This method builds `AbstractDataType`as following:
/// ```
/// AbstractDataType::Structure(
/// Structure {
/// name: vec!["org", "example", "Foo"], // assuming the namespace is `org.example`
/// fields: {
/// a: FieldReference { FullyQualifiedTypeReference { type_name: vec!["String"], parameters: vec![] }, FieldPresence::Optional },
/// b: FieldReference { FullyQualifiedTypeReference { type_name: vec!["int"], parameters: vec![] }, FieldPresence::Optional },
/// }, // HashMap with fields defined through `fields` constraint above
/// doc_comment: None // There is no doc comment defined in above ISL type def
/// source: IslType {name: "foo", .. } // Represents the `IslType` that is getting converted to `AbstractDataType`
/// is_closed: false, // If the fields constraint was annotated with `closed` then this would be true.
/// }
/// )
/// ```
fn build_structure_from_constraints(
&mut self,
constraints: &[IslConstraint],
code_gen_context: &mut CodeGenContext,
parent_isl_type: &IslType,
) -> CodeGenResult<AbstractDataType> {
let mut structure_builder = StructureBuilder::default();
structure_builder
.name(self.current_type_fully_qualified_name.to_owned())
.source(parent_isl_type.to_owned());
for constraint in constraints {
match constraint.constraint() {
IslConstraintValue::Fields(struct_fields, is_closed) => {
Expand All @@ -479,17 +524,11 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
}
// unwrap here is safe as the `current_abstract_data_type_builder` will either be initialized with default implementation
// or already initialized with a previous structure related constraint at this point.
structure_builder
.fields(fields)
.source(parent_isl_type.to_owned())
.is_closed(*is_closed)
.name(self.current_type_fully_qualified_name.to_owned());
structure_builder.fields(fields).is_closed(*is_closed);
}
IslConstraintValue::Type(_) => {
// by default fields aren't closed
structure_builder
.is_closed(false)
.source(parent_isl_type.to_owned());
structure_builder.is_closed(false);
}
_ => {
return invalid_abstract_data_type_error(
Expand All @@ -501,6 +540,122 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {

Ok(AbstractDataType::Structure(structure_builder.build()?))
}

/// Builds `AbstractDataType::WrappedScalar` from the given constraints.
/// ```
/// type::{
/// name: Foo,
/// type: string,
/// }
/// ```
/// This method builds `AbstractDataType`as following:
/// ```
/// AbstractDataType::WrappedScalar(
/// WrappedScalar {
/// name: vec!["org", "example", "Foo"], // assuming the namespace is `org.example`
/// base_type: FullyQualifiedTypeReference { type_name: vec!["String"], parameters: vec![] }
/// doc_comment: None // There is no doc comment defined in above ISL type def
/// source: IslType {name: "foo", .. } // Represents the `IslType` that is getting converted to `AbstractDataType`
/// }
/// )
/// ```
///
/// _Note: Currently code generator would return an error when there are multiple `type` constraints in the type definition.
/// This avoids providing conflicting type constraints in the type definition._
fn build_wrapped_scalar_from_constraints(
&mut self,
constraints: &[IslConstraint],
code_gen_context: &mut CodeGenContext,
parent_isl_type: &IslType,
) -> CodeGenResult<AbstractDataType> {
let mut wrapped_scalar_builder = WrappedScalarBuilder::default();
wrapped_scalar_builder
.name(self.current_type_fully_qualified_name.to_owned())
.source(parent_isl_type.to_owned());

let mut found_base_type = false;
for constraint in constraints {
match constraint.constraint() {
IslConstraintValue::Type(isl_type) => {
if found_base_type {
return invalid_abstract_data_type_error("Multiple `type` constraints in the type definitions are not supported in code generation as it can lead to conflicting types.");
}
let type_name = self
.fully_qualified_type_ref_name(isl_type, code_gen_context)?
.ok_or(invalid_abstract_data_type_raw_error(format!(
"Could not determine `FullQualifiedTypeReference` for type {:?}",
isl_type
)))?;

// by default fields aren't closed
wrapped_scalar_builder.base_type(type_name);
found_base_type = true;
}
_ => {
return invalid_abstract_data_type_error(
"Could not determine the abstract data type due to conflicting constraints",
);
}
}
}

Ok(AbstractDataType::WrappedScalar(
wrapped_scalar_builder.build()?,
))
}

/// Builds `AbstractDataType::Scalar` from the given constraints.
/// ```
/// { type: string }
/// ```
/// This method builds `AbstractDataType`as following:
/// ```
/// AbstractDataType::Scalar(
/// Scalar {
/// base_type: FullyQualifiedTypeReference { type_name: vec!["String"], parameters: vec![] }
/// doc_comment: None // There is no doc comment defined in above ISL type def
/// source: IslType { .. } // Represents the `IslType` that is getting converted to `AbstractDataType`
/// }
/// )
/// ```
///
/// _Note: Currently code generator would return an error when there are multiple `type` constraints in the type definition.
/// This avoids providing conflicting type constraints in the type definition._
fn build_scalar_from_constraints(
&mut self,
constraints: &[IslConstraint],
code_gen_context: &mut CodeGenContext,
parent_isl_type: &IslType,
) -> CodeGenResult<AbstractDataType> {
let mut scalar_builder = ScalarBuilder::default();
scalar_builder.source(parent_isl_type.to_owned());

let mut found_base_type = false;
for constraint in constraints {
match constraint.constraint() {
IslConstraintValue::Type(isl_type) => {
if found_base_type {
return invalid_abstract_data_type_error("Multiple `type` constraints in the type definitions are not supported in code generation as it can lead to conflicting types.");
}
let type_name = self
.fully_qualified_type_ref_name(isl_type, code_gen_context)?
.ok_or(invalid_abstract_data_type_raw_error(
"Could not determine `FullQualifiedTypeReference` for `struct`, `list` or `sexp` as open ended container types aren't supported."
))?;

scalar_builder.base_type(type_name);
found_base_type = true;
}
_ => {
return invalid_abstract_data_type_error(
"Could not determine the abstract data type due to conflicting constraints",
);
}
}
}

Ok(AbstractDataType::Scalar(scalar_builder.build()?))
}
}

#[cfg(test)]
Expand Down Expand Up @@ -535,6 +690,7 @@ mod isl_to_model_tests {
&"my_struct".to_string(),
&isl_type,
&mut CodeGenContext::new(),
false,
)?;
let abstract_data_type = data_model_node.code_gen_type.unwrap();
assert_eq!(
Expand Down Expand Up @@ -620,6 +776,7 @@ mod isl_to_model_tests {
&"my_nested_struct".to_string(),
&isl_type,
&mut CodeGenContext::new(),
false,
)?;
let abstract_data_type = data_model_node.code_gen_type.unwrap();
assert_eq!(
Expand Down
48 changes: 22 additions & 26 deletions src/bin/ion/commands/generate/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ impl AbstractDataType {
AbstractDataType::WrappedScalar(w) => {
Some(w.fully_qualified_type_name().to_owned().into())
}
AbstractDataType::Scalar(s) => Some(s.name.to_owned().into()),
AbstractDataType::Scalar(s) => Some(s.base_type.to_owned()),
AbstractDataType::Sequence(seq) => Some(seq.element_type.to_owned()),
AbstractDataType::Structure(structure) => Some(structure.name.to_owned().into()),
}
Expand All @@ -226,10 +226,11 @@ pub struct Scalar {
// element: string // this is a nested scalar type
// }
// ```
// Corresponding `FullyQualifiedName` would be `vec!["String"]`.
name: FullyQualifiedTypeName,
// Corresponding `FullyQualifiedReference` would be `FullyQualifiedTypeReference { type_name: vec!["String"], parameters: vec![] }`.
base_type: FullyQualifiedTypeReference,
// Represents doc comment for the generated code
// If the doc comment is provided for this scalar type then this is `Some(doc_comment)`, other it is None.
#[builder(default)]
doc_comment: Option<String>,
// Represents the source ISL type which can be used to get other constraints useful for this type.
// For example, getting the length of this sequence from `container_length` constraint or getting a `regex` value for string type.
Expand Down Expand Up @@ -265,16 +266,12 @@ pub struct WrappedScalar {
// type: string
// }
// ```
// Corresponding `FullyQualifiedTypeReference` would be as following:
// ```
// FullyQualifiedTypeReference {
// type_name: vec!["Foo"], // name of the wrapped scalar type
// parameters: vec![FullyQualifiedTypeReference {type_name: vec!["String"] }] // base type name for the scalar value
// }
// ```
name: FullyQualifiedTypeReference,
// Corresponding `name` would be `vec!["Foo"]` and `base_type` would be `FullyQualifiedTypeReference { type_name: vec!["String"], parameters: vec![] }`.
name: FullyQualifiedTypeName,
base_type: FullyQualifiedTypeReference,
// Represents doc comment for the generated code
// If the doc comment is provided for this scalar type then this is `Some(doc_comment)`, other it is None.
#[builder(default)]
doc_comment: Option<String>,
// Represents the source ISL type which can be used to get other constraints useful for this type.
// For example, getting the length of this sequence from `container_length` constraint or getting a `regex` value for string type.
Expand All @@ -286,7 +283,7 @@ pub struct WrappedScalar {

impl WrappedScalar {
pub fn fully_qualified_type_name(&self) -> &FullyQualifiedTypeName {
&self.name.type_name
&self.name
}
}

Expand Down Expand Up @@ -396,7 +393,10 @@ mod model_tests {
#[test]
fn scalar_builder_test() {
let expected_scalar = Scalar {
name: vec![],
base_type: FullyQualifiedTypeReference {
type_name: vec!["String".to_string()],
parameters: vec![],
},
doc_comment: Some("This is scalar type".to_string()),
source: anonymous_type(vec![type_constraint(named_type_ref("string"))]),
};
Expand All @@ -405,7 +405,7 @@ mod model_tests {

// sets all the information about the scalar type
scalar_builder
.name(vec![])
.base_type(vec!["String".to_string()])
.doc_comment(Some("This is scalar type".to_string()))
.source(anonymous_type(vec![type_constraint(named_type_ref(
"string",
Expand All @@ -418,12 +418,10 @@ mod model_tests {
#[test]
fn wrapped_scalar_builder_test() {
let expected_scalar = WrappedScalar {
name: FullyQualifiedTypeReference {
type_name: vec!["Foo".to_string()],
parameters: vec![FullyQualifiedTypeReference {
type_name: vec!["String".to_string()],
parameters: vec![],
}],
name: vec!["Foo".to_string()],
base_type: FullyQualifiedTypeReference {
type_name: vec!["String".to_string()],
parameters: vec![],
},
doc_comment: Some("This is scalar type".to_string()),
source: anonymous_type(vec![type_constraint(named_type_ref("string"))]),
Expand All @@ -433,12 +431,10 @@ mod model_tests {

// sets all the information about the scalar type
scalar_builder
.name(FullyQualifiedTypeReference {
type_name: vec!["Foo".to_string()],
parameters: vec![FullyQualifiedTypeReference {
type_name: vec!["String".to_string()],
parameters: vec![],
}],
.name(vec!["Foo".to_string()])
.base_type(FullyQualifiedTypeReference {
type_name: vec!["String".to_string()],
parameters: vec![],
})
.doc_comment(Some("This is scalar type".to_string()))
.source(anonymous_type(vec![type_constraint(named_type_ref(
Expand Down
Loading

0 comments on commit f3ae683

Please sign in to comment.