Skip to content

Commit

Permalink
Adds changes to support sequence types with type constraint
Browse files Browse the repository at this point in the history
* Modifies `AbstractDataType::Sequence` to store sequence type (sexp or
list) and element type
* Adds `element_type()` and `sequence_type()` methods on
`AbstractDataType` to get element type and sequence type details
* Renames `verify_abstract_data_type_consistency` to
`verify_and_update_abstract_data_type`
* `verify_and_update_abstract_data_type` modified to check for special
cases when `type` and `element` cosntraints occur together
* `JavaLanguage` and `RustLanguage` updated `target_type` to return
generic type for `List` and `SExp`
* Adds `wrapper_class` in `JavaLanguage` to be sued within `ArrayList`
  • Loading branch information
desaikd committed Apr 30, 2024
1 parent 515bd90 commit c386358
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 14 deletions.
26 changes: 23 additions & 3 deletions src/bin/ion/commands/beta/generate/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ pub enum AbstractDataType {
// }
// ```
Value,
// A series of zero or more values whose type is described by the nested `String` (e.g. a list)
// A series of zero or more values whose type is described by the nested `element_type`
// and sequence type is described by nested `sequence_type` (e.g. a list)
// e.g. Given below ISL,
// ```
// type::{
Expand All @@ -52,7 +53,10 @@ pub enum AbstractDataType {
// value: Vec<i64>
// }
// ```
Sequence(String),
Sequence {
element_type: String,
sequence_type: String,
},
// A collection of field name/value pairs (e.g. a map)
// the nested boolean represents whether the struct has closed fields or not
// e.g. Given below ISL,
Expand All @@ -75,14 +79,30 @@ pub enum AbstractDataType {
Structure(bool),
}

impl AbstractDataType {
pub fn element_type(&self) -> Option<&String> {
match self {
AbstractDataType::Sequence { element_type, .. } => Some(element_type),
_ => None,
}
}

pub fn sequence_type(&self) -> Option<&String> {
match self {
AbstractDataType::Sequence { sequence_type, .. } => Some(sequence_type),
_ => None,
}
}
}

impl Display for AbstractDataType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
AbstractDataType::Value => "single value struct",
AbstractDataType::Sequence(_) => "sequence value struct",
AbstractDataType::Sequence { .. } => "sequence value struct",
AbstractDataType::Structure(_) => "struct",
}
)
Expand Down
134 changes: 125 additions & 9 deletions src/bin/ion/commands/beta/generate/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,13 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
match constraint.constraint() {
IslConstraintValue::Element(isl_type, _) => {
let type_name = self.type_reference_name(isl_type)?;
self.verify_abstract_data_type_consistency(
AbstractDataType::Sequence(type_name.to_owned()),

self.verify_and_update_abstract_data_type(
AbstractDataType::Sequence {
element_type: type_name.to_owned(),
sequence_type: "list".to_string(),
},
tera_fields,
code_gen_context,
)?;
self.generate_struct_field(
Expand All @@ -325,8 +330,9 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
}
IslConstraintValue::Fields(fields, content_closed) => {
// TODO: Check for `closed` annotation on fields and based on that return error while reading if there are extra fields.
self.verify_abstract_data_type_consistency(
self.verify_and_update_abstract_data_type(
AbstractDataType::Structure(*content_closed),
tera_fields,
code_gen_context,
)?;
for (name, value) in fields.iter() {
Expand All @@ -343,11 +349,39 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
IslConstraintValue::Type(isl_type) => {
let type_name = self.type_reference_name(isl_type)?;

self.verify_abstract_data_type_consistency(
AbstractDataType::Value,
self.verify_and_update_abstract_data_type(
if isl_type.name() == "list" {
let abstract_data_type = AbstractDataType::Sequence {
element_type: type_name.clone(),
sequence_type: "list".to_string(),
};
abstract_data_type
} else if isl_type.name() == "sexp" {
let abstract_data_type = AbstractDataType::Sequence {
element_type: type_name.clone(),
sequence_type: "sexp".to_string(),
};
abstract_data_type
} else {
AbstractDataType::Value
},
tera_fields,
code_gen_context,
)?;
self.generate_struct_field(tera_fields, type_name, isl_type.name(), "value")?;

// if the abstract data type is a sequence then pass the type name as the updated `element_type`.
if let Some(AbstractDataType::Sequence { element_type, .. }) =
&code_gen_context.abstract_data_type
{
self.generate_struct_field(
tera_fields,
L::target_type_as_sequence(element_type),
isl_type.name(),
"value",
)?;
} else {
self.generate_struct_field(tera_fields, type_name, isl_type.name(), "value")?;
}
}
_ => {}
}
Expand All @@ -373,6 +407,7 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
/// Verify that the current abstract data type is same as previously determined abstract data type
/// This is referring to abstract data type determined with each constraint that is verifies
/// that all the constraints map to a single abstract data type and not different abstract data types.
/// Also, updates the underlying `element_type` for List and SExp.
/// e.g.
/// ```
/// type::{
Expand All @@ -386,14 +421,95 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> {
/// ```
/// For the above schema, both `fields` and `type` constraints map to different abstract data types
/// respectively Struct(with given fields `source` and `destination`) and Value(with a single field that has String data type).
fn verify_abstract_data_type_consistency(
fn verify_and_update_abstract_data_type(
&mut self,
current_abstract_data_type: AbstractDataType,
tera_fields: &mut Vec<Field>,
code_gen_context: &mut CodeGenContext,
) -> CodeGenResult<()> {
if let Some(abstract_data_type) = &code_gen_context.abstract_data_type {
if abstract_data_type != &current_abstract_data_type {
return invalid_abstract_data_type_error(format!("Can not determine abstract data type as current constraint {} conflicts with prior constraints for {}.", current_abstract_data_type, abstract_data_type));
match abstract_data_type {
// In the case when a `type` constraint occurs before `element` constraint. The element type for the sequence
// needs to be updated based on `element` constraint whereas sequence type will be used as per `type` constraint.
// e.g. For a schema as below:
// ```
// type::{
// name: sequence_type,
// type: sexp,
// element: string,
// }
// ```
// Here, first `type` constraint would set the `AbstractDataType::Sequence{ element_type: T, sequence_type: "sexp"}`
// which uses generic type T and sequence type is sexp. Next `element` constraint would
// set the `AbstractDataType::Sequence{ element_type: String, sequence_type: "list"}`.
// Now this method performs verification that if the above described case occurs
// then it updates the `element_type` as per `element` constraint
// and `sequence_type` as per `type` constraint.
AbstractDataType::Sequence {
element_type,
sequence_type,
} if abstract_data_type != &current_abstract_data_type
&& (element_type == "Object" || element_type == "T")
&& matches!(
&current_abstract_data_type,
&AbstractDataType::Sequence { .. }
) =>
{
// if current abstract data type is sequence and element_type is generic T or Object,
// then this was set by a `type` constraint in sequence field,
// so remove all previous fields that allows `Object` and update with current abstract_data_type.
tera_fields.pop();
code_gen_context.with_abstract_data_type(AbstractDataType::Sequence {
element_type: current_abstract_data_type
.element_type()
.unwrap()
.to_string(),
sequence_type: sequence_type.to_string(),
});
}
// In the case when a `type` constraint occurs before `element` constraint. The element type for the sequence
// needs to be updated based on `element` constraint whereas sequence type will be used as per `type` constraint.
// e.g. For a schema as below:
// ```
// type::{
// name: sequence_type,
// element: string,
// type: sexp,
// }
// ```
// Here, first `element` constraint would set the `AbstractDataType::Sequence{ element_type: String, sequence_type: "list"}` ,
// Next `type` constraint would set the `AbstractDataType::Sequence{ element_type: T, sequence_type: "sexp"}`
// which uses generic type `T` and sequence type is sexp. Now this method performs verification that
// if the above described case occurs then it updates the `element_type` as per `element` constraint
// and `sequence_type` as per `type` constraint.
AbstractDataType::Sequence { element_type, .. }
if abstract_data_type != &current_abstract_data_type
&& (current_abstract_data_type.element_type()
== Some(&"Object".to_string())
|| current_abstract_data_type.element_type()
== Some(&"T".to_string()))
&& matches!(
&current_abstract_data_type,
&AbstractDataType::Sequence { .. }
) =>
{
// if `element` constraint has already set the abstract data_type to `Sequence`
// then remove previous fields as new fields will be added again after updating `element_type`.
// `type` constraint does update the ISL type name to either `list` or `sexp`,
// which needs to be updated within `abstract_data_type` as well.
tera_fields.pop();
code_gen_context.with_abstract_data_type(AbstractDataType::Sequence {
element_type: element_type.to_string(),
sequence_type: current_abstract_data_type
.sequence_type()
.unwrap()
.to_string(),
})
}
_ if abstract_data_type != &current_abstract_data_type => {
return invalid_abstract_data_type_error(format!("Can not determine abstract data type as current constraint {} conflicts with prior constraints for {}.", current_abstract_data_type, abstract_data_type));
}
_ => {}
}
} else {
code_gen_context.with_abstract_data_type(current_abstract_data_type);
Expand Down
32 changes: 30 additions & 2 deletions src/bin/ion/commands/beta/generate/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,21 @@ impl Language for JavaLanguage {
Float => "double",
Bool => "boolean",
Blob | Clob => "byte[]",
List | SExp => "Object",
SchemaDefined(name) => name,
}
.to_string()
}

fn target_type_as_sequence(target_type: &str) -> String {
format!("ArrayList<{}>", target_type)
match JavaLanguage::wrapper_class(target_type) {
Some(wrapper_class_name) => {
format!("ArrayList<{}>", wrapper_class_name)
}
None => {
format!("ArrayList<{}>", target_type)
}
}
}

fn field_name_case() -> Case {
Expand All @@ -98,6 +106,21 @@ impl Language for JavaLanguage {
}
}

impl JavaLanguage {
fn wrapper_class(primitive_data_type_name: &str) -> Option<&str> {
match primitive_data_type_name {
"int" => Some("Integer"),
"bool" => Some("Boolean"),
"double" => Some("Double"),
"long" => Some("Long"),
_ => {
// for any other non-primitive types return None
None
}
}
}
}

impl Display for JavaLanguage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "java")
Expand Down Expand Up @@ -127,6 +150,7 @@ impl Language for RustLanguage {
Float => "f64",
Bool => "bool",
Blob | Clob => "Vec<u8>",
List | SExp => "T",
SchemaDefined(name) => name,
}
.to_string()
Expand Down Expand Up @@ -192,6 +216,8 @@ pub enum IonSchemaType {
Bool,
Blob,
Clob,
SExp,
List,
SchemaDefined(String), // A user defined schema type
}

Expand All @@ -215,9 +241,11 @@ impl From<&str> for IonSchemaType {
"decimal" | "timestamp" => {
unimplemented!("Decimal, Number and Timestamp aren't support yet!")
}
"list" | "struct" | "sexp" => {
"struct" => {
unimplemented!("Generic containers aren't supported yet!")
}
"list" => List,
"sexp" => SExp,
_ => SchemaDefined(value.to_case(Case::UpperCamel)),
}
}
Expand Down

0 comments on commit c386358

Please sign in to comment.