diff --git a/.github/workflows/branch-deploy.yml b/.github/workflows/branch-deploy.yml index b01a03ec0..e62853366 100644 --- a/.github/workflows/branch-deploy.yml +++ b/.github/workflows/branch-deploy.yml @@ -2,6 +2,11 @@ name: Deploy a branch as a snapshot version. on: workflow_dispatch: + inputs: + run-tests: + type: boolean + description: Run tests + default: true # Cancel previous jobs concurrency: @@ -12,15 +17,28 @@ jobs: deploy-branch: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Normalize branch name - id: normalize-branch-name - uses: ./.github/actions/normalize-branch-name - - name: Set version - run: mvn -B versions:set -DnewVersion=0.0.0.${{ steps.normalize-branch-name.outputs.normalized }}-SNAPSHOT - - uses: ./.github/actions/maven-build - with: - build-command: deploy # Deploy a snapshot build of main - env: - CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} - CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} + - uses: actions/checkout@v4 + - name: Set up Maven Central Repository + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + architecture: x64 + cache: maven + server-id: ossrh + server-username: CI_DEPLOY_USERNAME + server-password: CI_DEPLOY_PASSWORD + gpg-private-key: ${{ secrets.RUNE_GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + - name: Normalize branch name + id: normalize-branch-name + uses: ./.github/actions/normalize-branch-name + - name: Set version + run: mvn -B versions:set -DgenerateBackupPoms=false -DnewVersion=0.0.0.${{ steps.normalize-branch-name.outputs.normalized }}-SNAPSHOT + - uses: ./.github/actions/maven-build + with: + build-command: deploy + run-tests: ${{ inputs.run-tests }} + env: + CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} + CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} diff --git a/rosetta-ide/rosetta.tmLanguage.yaml b/rosetta-ide/rosetta.tmLanguage.yaml index b2bc02fb4..a9412c8b5 100644 --- a/rosetta-ide/rosetta.tmLanguage.yaml +++ b/rosetta-ide/rosetta.tmLanguage.yaml @@ -27,12 +27,13 @@ variables: # - keywords that may also be used as an identifier (e.g., `version`), # - keywords that may also be used as another keyword in a certain context (e.g., `synonym`), # - keywords that are unambiguous, i.e., all other keywords indicating the start of a root element. - identifiersConflictingWithRootStart: '{{wordStart}}(version){{wordEnd}}' + identifiersConflictingWithRootStart: '{{wordStart}}(translate){{wordEnd}}' + identifiersConflictingWithNamespace: '{{wordStart}}(version){{wordEnd}}' ambiguousRootStart: '{{wordStart}}(synonym|rule){{wordEnd}}' unambiguousRootStart: '{{wordStart}}(namespace|import|isEvent|isProduct|body|corpus|segment|basicType|recordType|typeAlias|library|reporting|eligibility|metaType|report|annotation|enum|type|choice|func){{wordEnd}}' - rootStart: '{{identifiersConflictingWithRootStart}}|{{ambiguousRootStart}}|{{unambiguousRootStart}}' - strictRootEnd: (?={{rootStart}}) - rootEnd: (?={{ambiguousRootStart}}|{{unambiguousRootStart}}) + rootStart: '{{identifiersConflictingWithRootStart}}|{{identifiersConflictingWithNamespace}}|{{ambiguousRootStart}}|{{unambiguousRootStart}}' + namespaceEnd: (?={{identifiersConflictingWithNamespace}}|{{ambiguousRootStart}}|{{unambiguousRootStart}}) + rootEnd: (?={{identifiersConflictingWithRootStart}}|{{ambiguousRootStart}}|{{unambiguousRootStart}}) unambiguousRootEnd: (?={{unambiguousRootStart}}) sectionStart: '{{wordStart}}((\Qpost-\E)?condition|set|add|inputs|output|alias){{wordEnd}}' @@ -45,7 +46,7 @@ variables: synonymAnnotationSectionEnd: (?={{synonymAnnotationSection}})|(?=\])|{{rootEnd}} docReferenceAnnotationSection: '{{wordStart}}(rationale|rationale_author|structured_provision|provision|reportedField){{wordEnd}}' docReferenceAnnotationSectionEnd: (?={{docReferenceAnnotationSection}}|\])|{{sectionEnd}} - expressionEnd: '(?=,|\)|\])|{{sectionEnd}}' + expressionEnd: '(?=,|\)|\]|\})|{{sectionEnd}}' functionalOperationEnd: (?={{listOperation}}|{{wordStart}}(else|then){{wordEnd}})|{{expressionEnd}} patterns: @@ -78,7 +79,7 @@ repository: begin: '{{wordStart}}namespace{{wordEnd}}' beginCaptures: 0: { name: keyword.other.namespace.rosetta } - end: '{{strictRootEnd}}' + end: '{{namespaceEnd}}' patterns: - include: '#comment' - include: '#documentation' @@ -146,6 +147,7 @@ repository: - include: '#rosettaTypeAlias' - include: '#rosettaLibraryFunction' - include: '#rosettaAnnotationSource' + - include: '#rosettaTranslateSource' - include: '#rosettaRule' - include: '#rosettaMetaType' - include: '#rosettaReport' @@ -453,6 +455,74 @@ repository: - include: '#comment' - include: '#annotation' + rosettaTranslateSource: + name: meta.translate-source.rosetta + begin: '{{wordStart}}translate{{wordEnd}}' + beginCaptures: + 0: { name: keyword.other.translate.rosetta } + end: (\})|{{rootEnd}} + endCaptures: + 1: { name: punctuation.section.braces.end.rosetta } + patterns: + - include: '#comment' + - begin: '(?<={{wordStart}}translate{{wordEnd}})' + end: (?=\{)|{{rootEnd}} + patterns: + - include: '#comment' + - name: keyword.other.source.rosetta + match: '{{wordStart}}source{{wordEnd}}' + - name: keyword.other.extends.rosetta + match: '{{wordStart}}extends{{wordEnd}}' + - name: entity.name.translate-source.rosetta + match: '{{identifier}}' + - name: meta.translate-source-body.rosetta + begin: (\{) + beginCaptures: + 1: { name: punctuation.section.braces.begin.rosetta } + end: (?=\})|{{unambiguousRootEnd}} + patterns: + - include: '#comment' + - name: meta.translation.inputs.rosetta + begin: ({{wordStart}}from{{wordEnd}}) + beginCaptures: + 1: { name: keyword.other.from.rosetta } + end: (?=\:|\})|{{rootEnd}} + patterns: + - include: '#comment' + - begin: '(?={{identifier}})' + end: '(,)|(?=\:|\})|{{rootEnd}}' + endCaptures: + 1: { name: punctuation.separator.comma.rosetta } + patterns: + - include: '#comment' + - begin: '({{identifier}})\s+(?={{identifier}})' + beginCaptures: + 1: { name: variable.parameter.input.rosetta } + end: '(?=,)|(?=\:|\})|{{rootEnd}}' + patterns: + - include: '#comment' + - include: '#typeCall' + - include: '#typeCall' + - name: meta.translation.result-type.rosetta + begin: '(?={{identifier}})' + end: (?={{wordStart}}from{{wordEnd}})|(?=\:|\})|{{rootEnd}} + patterns: + - include: '#comment' + - name: keyword.other.from.rosetta + match: '{{wordStart}}from{{wordEnd}}' + - include: '#typeCall' + - include: '#colon' + - include: '#translateAnnotation' + - name: meta.translation-attribute-mapping.rosetta + begin: '(\+|-)\s*({{identifier}})' + beginCaptures: + 1: { name: keyword.operator.annotation.rosetta } + 2: { name: variable.other.member.rosetta } + end: (?={{identifier}}|\}|\+|-)|{{rootEnd}} + patterns: + - include: '#comment' + - include: '#translateAnnotation' + rosettaRule: patterns: - name: meta.rule.rosetta @@ -722,6 +792,39 @@ repository: - include: '#comment' - name: entity.name.rule.rosetta match: '{{identifier}}' + + translateAnnotation: + name: meta.annotated.translate.rosetta + begin: (\[) + beginCaptures: + 1: { name: punctuation.annotation.begin.rosetta } + end: (\])|{{unambiguousRootEnd}} + endCaptures: + 1: { name: punctuation.annotation.end.rosetta } + patterns: + - include: '#comment' + - include: '#translateInstructionAnnotationBody' + - include: '#translateMetaInstructionAnnotationBody' + + translateInstructionAnnotationBody: + begin: '{{wordStart}}from{{wordEnd}}' + beginCaptures: + 0: { name: keyword.other.from.rosetta } + end: (?=\]|\})|{{rootEnd}} + patterns: + - include: '#comment' + - include: '#expression' + + translateMetaInstructionAnnotationBody: + begin: '{{wordStart}}meta{{wordEnd}}' + beginCaptures: + 0: { name: keyword.other.meta.rosetta } + end: (?=\]|\})|{{rootEnd}} + patterns: + - include: '#comment' + - include: '#translateInstructionAnnotationBody' + - name: variable.other.member.meta.rosetta + match: '{{identifier}}' customAnnotationBody: name: meta.annotated.rosetta diff --git a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/semantictokens/RosettaSemanticTokensService.java b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/semantictokens/RosettaSemanticTokensService.java index d3b1508b7..d610574f6 100644 --- a/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/semantictokens/RosettaSemanticTokensService.java +++ b/rosetta-ide/src/main/java/com/regnosys/rosetta/ide/semantictokens/RosettaSemanticTokensService.java @@ -36,6 +36,7 @@ import com.regnosys.rosetta.rosetta.RosettaSymbol; import com.regnosys.rosetta.rosetta.RosettaType; import com.regnosys.rosetta.rosetta.RosettaTypeAlias; +import com.regnosys.rosetta.rosetta.translate.TranslationParameter; import com.regnosys.rosetta.rosetta.TypeCall; import com.regnosys.rosetta.rosetta.TypeParameter; import com.regnosys.rosetta.rosetta.expression.ClosureParameter; @@ -264,6 +265,8 @@ private SemanticToken markSymbol(EObject objectToMark, EStructuralFeature featur return markAlias(objectToMark, featureToMark, (ShortcutDeclaration)symbol); } else if (symbol instanceof TypeParameter) { return createSemanticToken(objectToMark, featureToMark, PARAMETER); + } else if (symbol instanceof TranslationParameter) { + return createSemanticToken(objectToMark, featureToMark, PARAMETER); } return null; } diff --git a/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/contentassist/ContentAssistTest.xtend b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/contentassist/ContentAssistTest.xtend index dcf11ee88..50dbfb288 100644 --- a/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/contentassist/ContentAssistTest.xtend +++ b/rosetta-ide/src/test/java/com/regnosys/rosetta/ide/contentassist/ContentAssistTest.xtend @@ -164,6 +164,7 @@ class ContentAssistTest extends AbstractRosettaLanguageServerTest { to-string -> to-string [[7, 27] .. [7, 27]] to-time -> to-time [[7, 27] .. [7, 27]] to-zoned-date-time -> to-zoned-date-time [[7, 27] .. [7, 27]] + translate -> translate [[7, 27] .. [7, 27]] type -> type [[7, 27] .. [7, 27]] typeAlias -> typeAlias [[7, 27] .. [7, 27]] * -> * [[7, 27] .. [7, 27]] diff --git a/rosetta-lang/model/Rosetta.xcore b/rosetta-lang/model/Rosetta.xcore index e668255b3..6c8353fa5 100644 --- a/rosetta-lang/model/Rosetta.xcore +++ b/rosetta-lang/model/Rosetta.xcore @@ -23,8 +23,11 @@ class RosettaModel extends RosettaDefinable { class Import { String importedNamespace + String namespaceAlias + } + /********************************************************************** * Common types */ @@ -45,16 +48,10 @@ interface RosettaTyped { } } -class RosettaFeature extends RosettaNamed { - /** - * @return The name or 'value' if it's null - */ - derived String getNameOrDefault get { - return name ?: 'value' - } +interface RosettaFeature extends RosettaNamed { } -class RosettaTypedFeature extends RosettaFeature, RosettaTyped {} +interface RosettaTypedFeature extends RosettaFeature, RosettaTyped {} /** * A named symbol that may be used in an expression to reference an object diff --git a/rosetta-lang/model/RosettaExpression.xcore b/rosetta-lang/model/RosettaExpression.xcore index c84d5fb3c..39c2e2fdd 100644 --- a/rosetta-lang/model/RosettaExpression.xcore +++ b/rosetta-lang/model/RosettaExpression.xcore @@ -14,7 +14,9 @@ import com.regnosys.rosetta.rosetta.RosettaFeature import com.regnosys.rosetta.rosetta.RosettaCallableWithArgs import com.regnosys.rosetta.rosetta.RosettaMapTestExpression import com.regnosys.rosetta.rosetta.RosettaTyped +import com.regnosys.rosetta.rosetta.TypeCall import com.regnosys.rosetta.rosetta.simple.Attribute +import com.regnosys.rosetta.rosetta.translate.TranslateSource import org.eclipse.emf.common.util.BasicEList @@ -170,6 +172,12 @@ class ConstructorKeyValuePair { contains RosettaExpression value } +class TranslateDispatchOperation extends RosettaExpression { + refers TranslateSource source + refers RosettaExpression[] inputs + refers TypeCall outputType +} + interface RosettaOperation extends RosettaExpression { String operator } diff --git a/rosetta-lang/model/RosettaTranslate.xcore b/rosetta-lang/model/RosettaTranslate.xcore new file mode 100644 index 000000000..756a36402 --- /dev/null +++ b/rosetta-lang/model/RosettaTranslate.xcore @@ -0,0 +1,56 @@ +@Ecore(nsURI="http://www.rosetta-model.com/RosettaTranslate") +@GenModel(fileExtensions="rosetta", modelDirectory="/com.regnosys.rosetta/emf-gen/main/java", operationReflection="false", + copyrightText="Copyright (c) REGnosys 2017 (www.regnosys.com)", forceOverwrite="true", updateClasspath="false", + complianceLevel="8.0", bundleManifest="false", modelPluginID="") +package com.regnosys.rosetta.rosetta.translate + +import com.regnosys.rosetta.rosetta.simple.Attribute +import com.regnosys.rosetta.rosetta.RosettaMetaType +import com.regnosys.rosetta.rosetta.RosettaNamed +import com.regnosys.rosetta.rosetta.RosettaRootElement +import com.regnosys.rosetta.rosetta.RosettaSymbol +import com.regnosys.rosetta.rosetta.RosettaTyped +import com.regnosys.rosetta.rosetta.TypeCall +import com.regnosys.rosetta.rosetta.expression.RosettaExpression +import com.regnosys.rosetta.rosetta.expression.TranslateDispatchOperation + +class TranslateSource extends RosettaRootElement, RosettaNamed { + refers TranslateSource[] superSources + contains Translation[] translations opposite source +} + +class Translation { + refers TranslateSource source opposite translations + + contains TypeCall resultType + contains TranslationParameter[] parameters + contains TranslateInstruction[] typeInstructions + contains TranslateMetaInstruction[] typeMetaInstructions + + contains TranslationRule[] rules opposite translation +} + +class TranslationParameter extends RosettaSymbol, RosettaTyped { + +} + +class TranslationRule { + refers Translation translation opposite rules + + refers Attribute attribute + contains TranslateInstruction[] instructions + contains TranslateMetaInstruction[] metaInstructions +} + +abstract class BaseTranslateInstruction { + contains TranslateDispatchOperation _internalDispatchExpression + + contains RosettaExpression[] expressions +} + +class TranslateInstruction extends BaseTranslateInstruction { +} + +class TranslateMetaInstruction extends BaseTranslateInstruction { + refers RosettaMetaType metaFeature +} diff --git a/rosetta-lang/pom.xml b/rosetta-lang/pom.xml index 780052cc2..abd23b65d 100644 --- a/rosetta-lang/pom.xml +++ b/rosetta-lang/pom.xml @@ -90,6 +90,7 @@ model/Rosetta.xcore model/RosettaSimple.xcore model/RosettaExpression.xcore + model/RosettaTranslate.xcore diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/GenerateRosetta.mwe2 b/rosetta-lang/src/main/java/com/regnosys/rosetta/GenerateRosetta.mwe2 index b07c45732..25035c19f 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/GenerateRosetta.mwe2 +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/GenerateRosetta.mwe2 @@ -60,6 +60,7 @@ Workflow { referencedResource = "platform:/resource/com.regnosys.rosetta/model/Rosetta.xcore" referencedResource = "platform:/resource/com.regnosys.rosetta/model/RosettaSimple.xcore" referencedResource = "platform:/resource/com.regnosys.rosetta/model/RosettaExpression.xcore" + referencedResource = "platform:/resource/com.regnosys.rosetta/model/RosettaTranslate.xcore" name = "com.regnosys.rosetta.Rosetta" fileExtensions = "rosetta" diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext b/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext index da47f984a..439a49ab2 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/Rosetta.xtext @@ -2,6 +2,7 @@ grammar com.regnosys.rosetta.Rosetta with org.eclipse.xtext.common.Terminals import "http://www.rosetta-model.com/Rosetta" import "http://www.rosetta-model.com/RosettaSimple" import "http://www.rosetta-model.com/RosettaExpression" +import "http://www.rosetta-model.com/RosettaTranslate" import "http://www.eclipse.org/emf/2002/Ecore" as ecore // general TODO: naming is inconsistent, e.g., @@ -26,10 +27,11 @@ QualifiedName: ; Import: - 'import' importedNamespace=QualifiedNameWithWildcard; - + 'import' importedNamespace=(QualifiedNameWithWildcard) ('as' namespaceAlias=ValidID)?; + QualifiedNameWithWildcard: QualifiedName ('.' '*')?; + RootElement: Annotation @@ -166,7 +168,7 @@ RosettaRootElement: RosettaBasicType | RosettaRecordType | RosettaLibraryFunction | RosettaSynonymSource | RosettaRule | RosettaMetaType | RosettaExternalSynonymSource | RosettaExternalRuleSource | RosettaReport | RosettaTypeAlias | - RosettaQualifiedType | RosettaCalculationType + RosettaQualifiedType | RosettaCalculationType | TranslateSource ; @@ -208,8 +210,8 @@ TypeParameterValidID: ; ValidID: - ID | 'condition' | 'source' | 'value' | 'version' // used in CDM model - | 'pattern' + ID | 'condition' | 'source' | 'value' | 'version' + | 'pattern' | 'translate' ; @@ -891,3 +893,41 @@ RosettaRule: ('as' identifier=STRING)?) ) ; + +/*********** + * Translate + ************/ +TranslateSource: + 'translate' 'source' RosettaNamed ('extends' superSources+=[TranslateSource|QualifiedName] (',' superSources+=[TranslateSource|QualifiedName])* )? + '{' + (translations+=Translation)* + '}' +; + +Translation: + resultType=TypeCall ('from' parameters+=TranslationParameter (',' parameters+=TranslationParameter)*)? ':' + (typeInstructions+=TranslateInstruction | typeMetaInstructions+=TranslateMetaInstruction)* + rules+=TranslationRule* +; + +TranslationParameter: + RosettaNamed? RosettaTyped +; + +TranslationRule: + '+' attribute=[Attribute|ValidID] + (instructions+=TranslateInstruction | metaInstructions+=TranslateMetaInstruction)* +; + +fragment BaseTranslateInstruction: + 'from' expressions+=RosettaCalcExpression (',' expressions+=RosettaCalcExpression)* ']' +; + +TranslateInstruction: + '[' BaseTranslateInstruction +; + +TranslateMetaInstruction: + '[' 'meta' metaFeature=[RosettaMetaType|ValidID] BaseTranslateInstruction +; + diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/RosettaStandaloneSetup.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/RosettaStandaloneSetup.xtend index b9476b023..551d04937 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/RosettaStandaloneSetup.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/RosettaStandaloneSetup.xtend @@ -8,6 +8,7 @@ import com.regnosys.rosetta.rosetta.simple.SimplePackage import org.eclipse.emf.ecore.EPackage import com.regnosys.rosetta.rosetta.expression.ExpressionPackage import com.google.inject.Injector +import com.regnosys.rosetta.rosetta.translate.TranslatePackage /** * Initialization support for running Xtext languages without Equinox extension registry. @@ -32,6 +33,9 @@ class RosettaStandaloneSetup extends RosettaStandaloneSetupGenerated { if (!EPackage.Registry.INSTANCE.containsKey(ExpressionPackage.eNS_URI)) { EPackage.Registry.INSTANCE.put(ExpressionPackage.eNS_URI, ExpressionPackage.eINSTANCE); } + if (!EPackage.Registry.INSTANCE.containsKey(TranslatePackage.eNS_URI)) { + EPackage.Registry.INSTANCE.put(TranslatePackage.eNS_URI, TranslatePackage.eINSTANCE); + } super.register(injector) } } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaFormatter.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaFormatter.xtend index eb5f60b5a..7335c894c 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaFormatter.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/formatting2/RosettaFormatter.xtend @@ -56,6 +56,12 @@ import com.regnosys.rosetta.rosetta.TypeParameter import com.regnosys.rosetta.rosetta.TypeCallArgument import javax.inject.Inject import com.regnosys.rosetta.rosetta.RosettaRule +import com.regnosys.rosetta.rosetta.translate.TranslateSource +import com.regnosys.rosetta.rosetta.translate.Translation +import com.regnosys.rosetta.rosetta.translate.TranslationRule +import com.regnosys.rosetta.rosetta.translate.TranslateInstruction +import com.regnosys.rosetta.rosetta.translate.TranslateMetaInstruction +import com.regnosys.rosetta.rosetta.translate.BaseTranslateInstruction class RosettaFormatter extends AbstractRosettaFormatter2 { @@ -756,6 +762,127 @@ class RosettaFormatter extends AbstractRosettaFormatter2 { format ] } + + def dispatch void format(TranslateSource translateSource, extension IFormattableDocument document) { + val extension translateSourceGrammarAccess = translateSourceAccess + + translateSource.regionFor.keyword(sourceKeyword_1) + .surround[oneSpace] + translateSource.regionFor.keyword(extendsKeyword_3_0) + .surround[oneSpace] + translateSource.regionFor.keywords(commaKeyword_3_2_0).forEach[ + prepend[noSpace] + append[oneSpace] + ] + + indentedBraces(translateSource, document) + + translateSource.translations.head + .prepend[newLine] + translateSource.translations.tail.forEach[ + prepend[setNewLines(2)] + ] + translateSource.translations.forEach[ + format + ] + } + + def dispatch void format(Translation translation, extension IFormattableDocument document) { + val extension translationGrammarAccess = translationAccess + + translation.regionFor.keyword(fromKeyword_1_0) + .surround[oneSpace] + translation.regionFor.keywords(commaKeyword_1_2_0).forEach[ + prepend[noSpace] + append[oneSpace] + ] + translation.regionFor.keyword(':').prepend[noSpace] + translation.indentInner(document) + translation.typeInstructions.forEach[ + prepend[newLine] + format + ] + translation.typeMetaInstructions.forEach[ + prepend[newLine] + format + ] + translation.rules.forEach[ + prepend[newLine] + format + ] + } + + def dispatch void format(TranslationRule rule, extension IFormattableDocument document) { + val extension translationRuleGrammarAccess = translationRuleAccess + + rule.regionFor.keyword(plusSignKeyword_0) + .append[oneSpace] + rule.indentInner(document) + rule.instructions.forEach[ + prepend[newLine] + format + ] + rule.metaInstructions.forEach[ + prepend[newLine] + format + ] + } + + private def void formatBaseInstruction(BaseTranslateInstruction baseInstruction, extension IFormattableDocument document) { + val extension baseTranslateInstructionGrammarAccess = baseTranslateInstructionAccess + + baseInstruction.regionFor.keywords(commaKeyword_2_0).forEach[ + prepend[noSpace] + ] + + formatInlineOrMultiline(document, baseInstruction, FormattingMode.NORMAL, + [extension doc | // case: short argument list + baseInstruction.regionFor.keyword(fromKeyword_0) + .append[oneSpace] + baseInstruction.regionFor.keyword(rightSquareBracketKeyword_3) + .prepend[noSpace] + baseInstruction.regionFor.keywords(commaKeyword_2_0).forEach[ + append[oneSpace] + ] + baseInstruction.expressions.forEach[format(doc)] + ], + [extension doc | // case: long argument list + baseInstruction.indentInner(doc) + interior( + baseInstruction.regionFor.keyword(fromKeyword_0) + .append[newLine], + baseInstruction.regionFor.keyword(rightSquareBracketKeyword_3) + .prepend[newLine], + [indent] + ) + baseInstruction.regionFor.keywords(commaKeyword_2_0).forEach[ + append[newLine] + ] + baseInstruction.expressions.forEach[format(doc)] + ] + ) + } + + def dispatch void format(TranslateInstruction instruction, extension IFormattableDocument document) { + val extension translateInstructionGrammarAccess = translateInstructionAccess + + instruction.regionFor.keyword(leftSquareBracketKeyword_0) + .append[noSpace] + + formatBaseInstruction(instruction, document) + } + + def dispatch void format(TranslateMetaInstruction metaInstruction, extension IFormattableDocument document) { + val extension translateMetaInstructionGrammarAccess = translateMetaInstructionAccess + + metaInstruction.regionFor.keyword(metaKeyword_1) + .prepend[noSpace] + .append[oneSpace] + metaInstruction.regionFor.assignment(metaFeatureAssignment_2) + .append[oneSpace] + + formatBaseInstruction(metaInstruction, document) + } def void indentedBraces(EObject eObject, extension IFormattableDocument document) { val lcurly = eObject.regionFor.keyword('{').prepend[newLine] diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/IdentifierRepresentationService.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/IdentifierRepresentationService.java index d06adcbff..700c5c9da 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/IdentifierRepresentationService.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/IdentifierRepresentationService.java @@ -27,7 +27,7 @@ public class IdentifierRepresentationService { private ImplicitVariableUtil implicitVarUtil; public ImplicitVariableRepresentation getImplicitVarInContext(EObject context) { - EObject definingContainer = implicitVarUtil.findContainerDefiningImplicitVariable(context).orElseThrow(); + EObject definingContainer = implicitVarUtil.findObjectDefiningImplicitVariable(context).orElseThrow(); return new ImplicitVariableRepresentation(definingContainer); } } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/RosettaGenerator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/RosettaGenerator.xtend index af52eafdc..a293436c9 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/RosettaGenerator.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/RosettaGenerator.xtend @@ -31,7 +31,6 @@ import org.eclipse.xtext.generator.IGenerator2 import org.eclipse.xtext.generator.IGeneratorContext import org.slf4j.Logger import org.slf4j.LoggerFactory -import com.regnosys.rosetta.generator.java.reports.RuleGenerator import com.regnosys.rosetta.generator.java.condition.ConditionGenerator import com.regnosys.rosetta.generator.java.reports.ReportGenerator import javax.inject.Inject @@ -42,6 +41,9 @@ import com.regnosys.rosetta.config.RosettaGeneratorsConfiguration import com.regnosys.rosetta.generator.java.expression.DeepPathUtilGenerator import com.regnosys.rosetta.utils.DeepFeatureCallUtil import com.regnosys.rosetta.types.RDataType +import com.regnosys.rosetta.generator.java.reports.RuleGenerator +import com.regnosys.rosetta.generator.java.translate.TranslationGenerator +import com.regnosys.rosetta.rosetta.translate.TranslateSource /** * Generates code from your model files on save. @@ -59,6 +61,7 @@ class RosettaGenerator implements IGenerator2 { @Inject ExternalGenerators externalGenerators @Inject JavaPackageInfoGenerator javaPackageInfoGenerator @Inject RuleGenerator ruleGenerator + @Inject TranslationGenerator translationGenerator @Inject ModelObjectGenerator dataGenerator @Inject ValidatorsGenerator validatorsGenerator @@ -194,6 +197,11 @@ class RosettaGenerator implements IGenerator2 { tabulatorGenerator.generate(fsa, externalClass.data, Optional.of(it)) ] } + TranslateSource: { + it.translations.forEach [ + translationGenerator.generate(fsa, it) + ] + } } ] enumGenerator.generate(packages, fsa, model.elements, version) diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/ExpressionGenerator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/ExpressionGenerator.xtend index be9a5a406..da6b3a767 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/ExpressionGenerator.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/ExpressionGenerator.xtend @@ -129,6 +129,16 @@ import java.time.ZonedDateTime import com.regnosys.rosetta.rosetta.expression.RosettaDeepFeatureCall import com.regnosys.rosetta.rosetta.expression.DefaultOperation import com.regnosys.rosetta.generator.java.statement.builder.JavaConditionalExpression +import com.regnosys.rosetta.rosetta.translate.TranslationParameter +import com.regnosys.rosetta.rosetta.expression.TranslateDispatchOperation +import com.regnosys.rosetta.utils.TranslateUtil +import java.util.ArrayList +import com.fasterxml.jackson.core.type.TypeReference +import com.regnosys.rosetta.generator.java.statement.JavaLocalVariableDeclarationStatement +import com.regnosys.rosetta.generator.java.statement.JavaForLoop +import com.regnosys.rosetta.generator.java.statement.JavaBlock +import com.regnosys.rosetta.generator.java.statement.JavaStatementList + class ExpressionGenerator extends RosettaExpressionSwitch { @@ -152,6 +162,7 @@ class ExpressionGenerator extends RosettaExpressionSwitchof(«it»)''', - MAPPER_C.wrap(itemType) - ) - ] + JavaStatementBuilder.invokeMethod( + elements, + [JavaExpression.from('''«MapperC».<«itemType»>of(«it»)''', MAPPER_C.wrap(itemType))], + context.scope + ) } override protected caseMapOperation(MapOperation expr, Context context) { @@ -1017,6 +1013,10 @@ class ExpressionGenerator extends RosettaExpressionSwitch input0List = ...; + // final List input1List = ...; + // ... + // final List outputList = new ArrayList<>(); + // for (int i = 0; i < input0List.size() && i < input1List.size() && ...; i++) + // outputList.add(dispatchFunc.evaluate(input0List.get(i), input1List.get(i), ...)); + // } + // outputList + + var argsListDeclarations = new JavaBlock(new JavaStatementList()); + val forScope = context.scope.childScope("for-loop") + val indexId = forScope.createUniqueIdentifier("i") + var JavaExpression forConditions = null + val itemArgs = newArrayList + for (var i = 0; i < expr.inputs.size; i++) { + // Declare inputs as variables of the form: + // List inputList0 = ; + // List inputList1 = ; + // ... + val itemType = rCallable.inputs.get(i).attributeToJavaType + val argsExpr = expr.inputs.get(i).javaCode(LIST.wrapExtendsIfNotFinal(itemType), context.scope) + val argListVar = context.scope.createUniqueIdentifier(itemType.simpleName.toFirstLower + "List") + argsListDeclarations = argsListDeclarations.append( + argsExpr.complete[ + new JavaLocalVariableDeclarationStatement(true, LIST.wrapExtendsIfNotFinal(itemType), argListVar, it) + ] + ) + // Create a condition of the form: + // i < inputList0.size() && i < inputList1.size() && ... + val cond = JavaExpression.from('''«indexId» < «argListVar».size()''', JavaPrimitiveType.BOOLEAN) + if (forConditions === null) { + forConditions = cond + } else { + val prev = forConditions + forConditions = JavaExpression.from('''«prev» && «cond»''', JavaPrimitiveType.BOOLEAN) + } + // Items of the inputs can be accessed using `inputList0.get(i)`, `inputList1.get(i)`, etc. + itemArgs.add(JavaExpression.from('''«argListVar».get(«indexId»)''', itemType)) + } + + val accId = context.scope.createUniqueIdentifier(javaOutputType.simpleName.toFirstLower + "List") + val accType = LIST.wrap(javaOutputType) + argsListDeclarations + .append( + new JavaLocalVariableDeclarationStatement( + true, + accType, + accId, + JavaExpression.from('''new «ArrayList»<>()''',JavaGenericTypeDeclaration.from(new TypeReference>() {}).wrap(javaOutputType)) + ) + ).append( + new JavaForLoop( + new JavaLocalVariableDeclarationStatement(false, JavaPrimitiveType.INT, indexId, JavaExpression.from('''0''', JavaPrimitiveType.INT)), + forConditions, + JavaExpression.from('''«indexId»++''', JavaPrimitiveType.INT), + JavaStatementBuilder.invokeMethod( + itemArgs, + [JavaExpression.from('''«accId».add(«context.scope.getIdentifierOrThrow(rCallable.toFunctionJavaClass.toDependencyInstance)».evaluate(«it»))''', JavaPrimitiveType.BOOLEAN)], + context.scope + ).completeAsExpressionStatement + ) + ).append( + new JavaVariable(accId, accType) + ) + } else { + val args = newArrayList + for (var i = 0; i < expr.inputs.size; i++) { + args.add(expr.inputs.get(i).javaCode(rCallable.inputs.get(i).attributeToJavaType, context.scope)) + } + JavaStatementBuilder.invokeMethod( + args, + [JavaExpression.from('''«context.scope.getIdentifierOrThrow(rCallable.toFunctionJavaClass.toDependencyInstance)».evaluate(«it»)''', javaOutputType)], + context.scope + ) + } + } + } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/JavaDependencyProvider.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/JavaDependencyProvider.xtend index 3c752ad2c..3c6cd087e 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/JavaDependencyProvider.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/expression/JavaDependencyProvider.xtend @@ -14,6 +14,9 @@ import com.regnosys.rosetta.types.RosettaTypeProvider import com.regnosys.rosetta.generator.java.types.JavaTypeTranslator import com.regnosys.rosetta.types.RDataType import java.util.List +import com.regnosys.rosetta.rosetta.expression.TranslateDispatchOperation +import com.regnosys.rosetta.utils.TranslateUtil +import com.regnosys.rosetta.types.TypeSystem /** * A class that helps determine which RosettaFunctions a Rosetta object refers to @@ -21,15 +24,28 @@ import java.util.List class JavaDependencyProvider { @Inject RObjectFactory rTypeBuilderFactory @Inject RosettaTypeProvider typeProvider + @Inject TypeSystem typeSystem @Inject extension JavaTypeTranslator + @Inject TranslateUtil translateUtil def List> javaDependencies(RosettaExpression expression) { val rosettaSymbols = EcoreUtil2.eAllOfType(expression, RosettaSymbolReference).map[it.symbol] val deepFeatureCalls = EcoreUtil2.eAllOfType(expression, RosettaDeepFeatureCall) + val translateDispatchOperations = EcoreUtil2.eAllOfType(expression, TranslateDispatchOperation) + val actualDispatches = newArrayList + for (op : translateDispatchOperations) { + val inputTypes = op.inputs.map[typeProvider.getRType(it)] + val outputType = typeSystem.typeCallToRType(op.outputType) + if (inputTypes.size !== 1 || !typeSystem.isSubtypeOf(inputTypes.head, outputType)) { + val match = translateUtil.findMatches(op.source, outputType, inputTypes).last + actualDispatches.add(rTypeBuilderFactory.buildRFunction(match)) + } + } ( rosettaSymbols.filter(Function).map[rTypeBuilderFactory.buildRFunction(it).toFunctionJavaClass] + rosettaSymbols.filter(RosettaRule).map[rTypeBuilderFactory.buildRFunction(it).toFunctionJavaClass] + - deepFeatureCalls.map[typeProvider.getRType(receiver)].filter(RDataType).map[data.toDeepPathUtilJavaClass] + deepFeatureCalls.map[typeProvider.getRType(receiver)].filter(RDataType).map[data.toDeepPathUtilJavaClass] + + actualDispatches.map[toFunctionJavaClass] ).toSet.sortBy[it.simpleName] } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/function/FunctionGenerator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/function/FunctionGenerator.xtend index 83ddba9e5..caab278ea 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/function/FunctionGenerator.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/function/FunctionGenerator.xtend @@ -68,6 +68,9 @@ import java.util.Collections import com.fasterxml.jackson.core.type.TypeReference import com.rosetta.util.types.JavaGenericTypeDeclaration import com.regnosys.rosetta.generator.java.expression.JavaDependencyProvider +import com.regnosys.rosetta.generator.java.RosettaJavaPackages +import com.rosetta.model.lib.meta.Reference +import com.rosetta.model.lib.meta.Key class FunctionGenerator { @@ -85,6 +88,7 @@ class FunctionGenerator { @Inject ImplicitVariableUtil implicitVariableUtil @Inject extension JavaTypeUtil @Inject TypeCoercionService coercionService + @Inject RosettaJavaPackages rosettaJavaPackages def void generate(RootPackage root, IFileSystemAccess2 fsa, Function func, String version) { val fileName = root.functions.withForwardSlashes + '/' + func.name + '.java' @@ -390,7 +394,7 @@ class FunctionGenerator { private def JavaStatement assign(JavaScope scope, ROperation op, RFunction function, Map outs, RAttribute attribute) { - if (op.pathTail.isEmpty) { + if (op.pathTail.isEmpty && !op.isMetaOperation) { // assign function output object val expressionType = attribute.attributeToJavaType var javaExpr = expressionGenerator.javaCode(op.expression, expressionType, scope) @@ -432,24 +436,57 @@ class FunctionGenerator { } } else { // assign an attribute of the function output object - assignValue(scope, op, op.assignAsKey, op.pathTail.last.multi) + val StringConcatenationClient assignPathCode = + ''' + «op.assignTarget(function, outs, scope)» + «FOR seg : op.pathTail.indexed» + «IF seg.key < op.pathTail.size - 1 || op.isMetaOperation» + .getOrCreate«seg.value.name.toFirstUpper»(«IF seg.value.multi»0«ENDIF»)«IF isReference(seg.value) && seg.key < op.pathTail.size - 1».getOrCreateValue()«ENDIF» + «ENDIF» + «ENDFOR» + ''' + assignValue(scope, op, op.assignAsKey) .collapseToSingleExpression(scope) .mapExpression[ JavaExpression.from( ''' - «op.assignTarget(function, outs, scope)» - «FOR seg : op.pathTail.indexed» - «IF seg.key < op.pathTail.size - 1» - .getOrCreate«seg.value.name.toFirstUpper»(«IF seg.value.multi»0«ENDIF»)«IF isReference(seg.value)».getOrCreateValue()«ENDIF» - «ELSE» - .«IF op.ROperationType == ROperationType.ADD»add«ELSE»set«ENDIF»«seg.value.name.toFirstUpper»«IF seg.value.isReference && !op.assignAsKey»Value«ENDIF»(«it»)«ENDIF»«ENDFOR»''', + «assignPathCode» + «IF op.isMetaOperation» + «IF op.metaFeature.name != "reference" && op.metaFeature.name != "address"» + .getOrCreateMeta() + «ENDIF» + «op.metaFeature.metaFeatureSetterCode(it)»« + ELSE» + .«IF op.ROperationType == ROperationType.ADD»add«ELSE»set«ENDIF»«op.pathTail.last.name.toFirstUpper»«IF op.pathTail.last.isReference && !op.assignAsKey»Value«ENDIF»(«it»)« + ENDIF»''', JavaPrimitiveType.VOID ) ].completeAsExpressionStatement } } + + private def String metaFeatureToJavaField(RAttribute metaFeature) { + switch metaFeature.name { + case "key": "externalKey" + case "id": "externalKey" + case "reference": "externalReference" + case "scheme": "scheme" + case "template": "template" + case "address": "reference" + case "location": "key" + } + } + private def StringConcatenationClient metaFeatureSetterCode(RAttribute metaFeature, JavaExpression v) { + if (metaFeature.name == "address") { + '''.set«metaFeature.metaFeatureToJavaField.toFirstUpper»(«Reference».builder().setScope("DOCUMENT").setReference(«v»))''' + } else if (metaFeature.name == "location") { + '''.add«metaFeature.metaFeatureToJavaField.toFirstUpper»(«Key».builder().setScope("DOCUMENT").setKeyValue(«v»))''' + } else { + '''.set«metaFeature.metaFeatureToJavaField.toFirstUpper»(«v»)''' + } + } - private def JavaStatementBuilder assignValue(JavaScope scope, ROperation op, boolean assignAsKey, boolean isAssigneeMulti) { + private def JavaStatementBuilder assignValue(JavaScope scope, ROperation op, boolean assignAsKey) { if (assignAsKey) { val metaClass = op.operationToReferenceWithMetaType if (cardinality.isMulti(op.expression)) { diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/JavaBlock.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/JavaBlock.java index 909de11bf..e0076d387 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/JavaBlock.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/JavaBlock.java @@ -18,6 +18,9 @@ import org.eclipse.xtend2.lib.StringConcatenationClient.TargetStringConcatenation; +import com.regnosys.rosetta.generator.java.statement.builder.JavaBlockBuilder; +import com.regnosys.rosetta.generator.java.statement.builder.JavaStatementBuilder; + /** * Based on the Java specification: https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-Block * diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/JavaForLoop.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/JavaForLoop.java new file mode 100644 index 000000000..117d6fc0a --- /dev/null +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/JavaForLoop.java @@ -0,0 +1,45 @@ +package com.regnosys.rosetta.generator.java.statement; + +import org.eclipse.xtend2.lib.StringConcatenationClient.TargetStringConcatenation; + +import com.regnosys.rosetta.generator.java.statement.builder.JavaExpression; + +/** + * Based on the Java specification: https://docs.oracle.com/javase/specs/jls/se11/html/jls-14.html#jls-BasicForStatement + * + * Example: + * ``` + * for (int i=0; i<42; i++) { + * list.add(i); + * } + * ``` + * + * See `JavaStatementBuilder` for more documentation. + */ +public class JavaForLoop extends JavaStatement { + private final JavaLocalVariableDeclarationStatement forInit; + private final JavaExpression forCondition; + private final JavaExpression forUpdate; + private final JavaStatement forBody; + + public JavaForLoop(JavaLocalVariableDeclarationStatement forInit, JavaExpression forCondition, JavaExpression forUpdate, JavaStatement forBody) { + this.forInit = forInit; + this.forCondition = forCondition; + this.forUpdate = forUpdate; + this.forBody = forBody; + } + + @Override + public void appendTo(TargetStringConcatenation target) { + target.append("for ("); + target.append(forInit); + target.append(" "); + target.append(forCondition); + target.append("; "); + target.append(forUpdate); + target.append(") "); + // Calling `toBlock()` will make sure that the body is always enclosed in curly braces. + // This is a style preference, and is technically not necessary. + target.append(forBody.toBlock()); + } +} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/JavaStatement.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/JavaStatement.java index bd914e2e9..3422fd47b 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/JavaStatement.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/JavaStatement.java @@ -17,6 +17,8 @@ package com.regnosys.rosetta.generator.java.statement; import com.regnosys.rosetta.generator.TargetLanguageRepresentation; +import com.regnosys.rosetta.generator.java.statement.builder.JavaBlockBuilder; +import com.regnosys.rosetta.generator.java.statement.builder.JavaStatementBuilder; /** * A representation of a statement in Java. Examples: @@ -108,4 +110,13 @@ public JavaBlock append(JavaStatement other) { } return new JavaBlock(JavaStatementList.of(this, other)); } + /** + * Append the given statement builder to this statement. Behaves the same as `builder.prepend(this)`. + * + * This operation flattens block statements. See #append(JavaStatement) for examples. + * ``` + */ + public JavaBlockBuilder append(JavaStatementBuilder builder) { + return new JavaBlockBuilder(this.asStatementList(), builder); + } } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/builder/JavaBlockBuilder.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/builder/JavaBlockBuilder.java index 0fb121787..890ff662c 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/builder/JavaBlockBuilder.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/builder/JavaBlockBuilder.java @@ -59,12 +59,12 @@ public JavaType getExpressionType() { } @Override - public JavaStatementBuilder mapExpression(Function mapper) { + public JavaBlockBuilder mapExpression(Function mapper) { return new JavaBlockBuilder(statements, lastStatement.mapExpression(mapper)); } @Override - public JavaStatementBuilder then(JavaStatementBuilder after, BiFunction combineExpressions, JavaScope scope) { + public JavaBlockBuilder then(JavaStatementBuilder after, BiFunction combineExpressions, JavaScope scope) { if (after instanceof JavaBlockBuilder) { return this.then((JavaBlockBuilder)after, combineExpressions, scope); } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/builder/JavaStatementBuilder.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/builder/JavaStatementBuilder.java index 3a4e62c20..8f2499a93 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/builder/JavaStatementBuilder.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/statement/builder/JavaStatementBuilder.java @@ -16,13 +16,18 @@ package com.regnosys.rosetta.generator.java.statement.builder; +import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; +import org.eclipse.xtend2.lib.StringConcatenationClient.TargetStringConcatenation; + import com.regnosys.rosetta.generator.GeneratedIdentifier; import com.regnosys.rosetta.generator.java.JavaScope; +import com.regnosys.rosetta.generator.java.statement.JavaBlock; import com.regnosys.rosetta.generator.java.statement.JavaLambdaBody; import com.regnosys.rosetta.generator.java.statement.JavaStatement; +import com.regnosys.rosetta.generator.java.statement.JavaStatementList; import com.rosetta.util.types.JavaType; /** @@ -52,7 +57,7 @@ * ``` * * *Example 2* - * Say we want to build the following block: + * Say we want to build the following block (we don't really care about the name of the variable): * ``` * { * int ifThenElseResult; @@ -85,6 +90,30 @@ * ``` */ public abstract class JavaStatementBuilder { + + public static JavaStatementBuilder invokeMethod(List arguments, Function methodInvoker, JavaScope scope) { + if (arguments.isEmpty()) { + return methodInvoker.apply(null); + } + JavaStatementBuilder argCode = arguments.get(0); + for (var i = 1; i < arguments.size(); i++) { + argCode = argCode.then( + arguments.get(i), + (argList, newArg) -> new JavaExpression(null) { + @Override + public void appendTo(TargetStringConcatenation target) { + target.append(argList); + target.append(", "); + target.append(newArg); + } + }, + scope + ); + } + return argCode.collapseToSingleExpression(scope).mapExpression(methodInvoker); + } + + /** * Get the type of the last expression of this builder, * or the least common supertype of all expressions in different branches of this builder. diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/translate/TranslationGenerator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/translate/TranslationGenerator.xtend new file mode 100644 index 000000000..529974652 --- /dev/null +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/translate/TranslationGenerator.xtend @@ -0,0 +1,31 @@ +package com.regnosys.rosetta.generator.java.translate + +import javax.inject.Inject +import org.eclipse.xtext.generator.IFileSystemAccess2 +import com.regnosys.rosetta.generator.java.util.ImportManagerExtension +import com.regnosys.rosetta.generator.java.types.JavaTypeTranslator +import com.regnosys.rosetta.types.RObjectFactory +import com.regnosys.rosetta.generator.java.JavaScope +import com.regnosys.rosetta.generator.java.function.FunctionGenerator +import com.regnosys.rosetta.rosetta.translate.Translation +import com.rosetta.util.types.JavaClass +import com.rosetta.model.lib.functions.RosettaFunction + +class TranslationGenerator { + @Inject extension JavaTypeTranslator + @Inject extension RObjectFactory + @Inject extension ImportManagerExtension + @Inject FunctionGenerator functionGenerator + + + def generate(IFileSystemAccess2 fsa, Translation translation) { + val rFunction = buildRFunction(translation) + val clazz = rFunction.toFunctionJavaClass + val baseInterface = JavaClass.from(RosettaFunction) + val topScope = new JavaScope(clazz.packageName) + val classBody = functionGenerator.rBuildClass(rFunction, false, #[baseInterface], emptyMap, false, topScope) + + val content = buildClass(clazz.packageName, classBody, topScope) + fsa.generateFile(clazz.canonicalName.withForwardSlashes + ".java", content) + } +} \ No newline at end of file diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/types/JavaTypeTranslator.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/types/JavaTypeTranslator.java index 413e2b909..2d188053a 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/types/JavaTypeTranslator.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/generator/java/types/JavaTypeTranslator.java @@ -118,6 +118,8 @@ public JavaClass toFunctionJavaClass(RFunction func) return generatedJavaClassService.toJavaReportFunction(func.getReportId()); case RULE: return generatedJavaClassService.toJavaRule(func.getSymbolId()); + case TRANSLATION: + return generatedJavaClassService.toJavaTranslationFunction(func.getTranslationId()); default: throw new IllegalStateException("Unknown origin of RFunction: " + func.getOrigin()); } @@ -268,6 +270,9 @@ public JavaClass operationToReferenceWithMetaType(Operation op) { public JavaReferenceType operationToJavaType(ROperation op) { RAttribute attr; + if (op.isMetaOperation()) { + return attributeToJavaType(op.getMetaFeature()); + } if (op.getPathTail().isEmpty()) { attr = (RAttribute)op.getPathHead(); // TODO: this won't work when assigning to an alias } else { diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/interpreter/RosettaInterpreter.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/interpreter/RosettaInterpreter.java index 2f42d61dc..b3d918a37 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/interpreter/RosettaInterpreter.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/interpreter/RosettaInterpreter.java @@ -81,6 +81,7 @@ import com.regnosys.rosetta.rosetta.expression.ToStringOperation; import com.regnosys.rosetta.rosetta.expression.ToTimeOperation; import com.regnosys.rosetta.rosetta.expression.ToZonedDateTimeOperation; +import com.regnosys.rosetta.rosetta.expression.TranslateDispatchOperation; import com.regnosys.rosetta.types.RosettaTypeProvider; import com.regnosys.rosetta.types.TypeSystem; import com.regnosys.rosetta.types.RType; @@ -580,7 +581,13 @@ protected RosettaValue caseToZonedDateTimeOperation(ToZonedDateTimeOperation exp @Override protected RosettaValue caseConstructorExpression(RosettaConstructorExpression expr, RosettaInterpreterContext context) { - // TODO Auto-generated method stub + // TODO throw new RosettaInterpreterException("Constructor expressions are not supported yet."); } + @Override + protected RosettaValue caseTranslateDispatchOperation(TranslateDispatchOperation expr, + RosettaInterpreterContext context) { + // TODO + throw new RosettaInterpreterException("Translate dispatch operations are not supported yet."); + } } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/scoping/AliasAwareImportNormalizer.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/scoping/AliasAwareImportNormalizer.java new file mode 100644 index 000000000..7188a536d --- /dev/null +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/scoping/AliasAwareImportNormalizer.java @@ -0,0 +1,73 @@ +package com.regnosys.rosetta.scoping; + +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.scoping.impl.ImportNormalizer; + +public class AliasAwareImportNormalizer extends ImportNormalizer { + private final QualifiedName namespaceAlias; + private static final String NULL_ALIAS_ERROR = AliasAwareImportNormalizer.class.getSimpleName() + " does not support null namespaceAlias values"; + + public AliasAwareImportNormalizer(QualifiedName importedNamespace, String namespaceAlias, boolean wildCard, + boolean ignoreCase) { + super(importedNamespace, wildCard, ignoreCase); + this.namespaceAlias = namespaceAlias != null ? QualifiedName.create(namespaceAlias) : null; + } + + @Override + public QualifiedName deresolve(QualifiedName fullyQualifiedName) { + if (namespaceAlias != null) { + QualifiedName deresolved = super.deresolve(fullyQualifiedName); + if (deresolved != null) { + return namespaceAlias.append(deresolved); + } + return null; + } else { + throw new IllegalStateException(NULL_ALIAS_ERROR); + } + } + + @Override + public QualifiedName resolve(QualifiedName relativeName) { + if (relativeName.isEmpty()) + return null; + + if (namespaceAlias != null && relativeName.startsWith(namespaceAlias) + && relativeName.getSegmentCount() != namespaceAlias.getSegmentCount()) { + return super.resolve(relativeName.skipFirst(namespaceAlias.getSegmentCount())); + } else { + throw new IllegalStateException(NULL_ALIAS_ERROR); + } + } + + @Override + public String toString() { + return getImportedNamespacePrefix().toString() + (hasWildCard() ? ".*" : "") + + (namespaceAlias != null ? "as " + namespaceAlias : ""); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = super.hashCode(); + result = prime * result + namespaceAlias.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (super.equals(obj) == false) { + return false; + } + if (obj instanceof AliasAwareImportNormalizer) { + AliasAwareImportNormalizer other = (AliasAwareImportNormalizer) obj; + return other.namespaceAlias.equals(namespaceAlias); + } + return false; + } + +} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/scoping/RosettaScopeProvider.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/scoping/RosettaScopeProvider.xtend index ba53d26a2..fbbcf0d35 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/scoping/RosettaScopeProvider.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/scoping/RosettaScopeProvider.xtend @@ -6,15 +6,27 @@ package com.regnosys.rosetta.scoping import com.google.common.base.Predicate import com.regnosys.rosetta.RosettaExtensions import com.regnosys.rosetta.generator.util.RosettaFunctionExtensions +import com.regnosys.rosetta.rosetta.ParametrizedRosettaType +import com.regnosys.rosetta.rosetta.RosettaAttributeReference import com.regnosys.rosetta.rosetta.RosettaEnumValueReference import com.regnosys.rosetta.rosetta.RosettaEnumeration import com.regnosys.rosetta.rosetta.RosettaExternalClass import com.regnosys.rosetta.rosetta.RosettaExternalEnum import com.regnosys.rosetta.rosetta.RosettaExternalEnumValue import com.regnosys.rosetta.rosetta.RosettaExternalRegularAttribute -import com.regnosys.rosetta.rosetta.expression.RosettaFeatureCall import com.regnosys.rosetta.rosetta.RosettaModel +import com.regnosys.rosetta.rosetta.RosettaTypeAlias +import com.regnosys.rosetta.rosetta.TypeCall +import com.regnosys.rosetta.rosetta.expression.ChoiceOperation +import com.regnosys.rosetta.rosetta.expression.ConstructorKeyValuePair +import com.regnosys.rosetta.rosetta.expression.InlineFunction +import com.regnosys.rosetta.rosetta.expression.RosettaConstructorExpression +import com.regnosys.rosetta.rosetta.expression.RosettaDeepFeatureCall +import com.regnosys.rosetta.rosetta.expression.RosettaFeatureCall +import com.regnosys.rosetta.rosetta.expression.RosettaSymbolReference +import com.regnosys.rosetta.rosetta.simple.Annotated import com.regnosys.rosetta.rosetta.simple.AnnotationRef +import com.regnosys.rosetta.rosetta.simple.Attribute import com.regnosys.rosetta.rosetta.simple.Condition import com.regnosys.rosetta.rosetta.simple.Data import com.regnosys.rosetta.rosetta.simple.Function @@ -22,42 +34,38 @@ import com.regnosys.rosetta.rosetta.simple.FunctionDispatch import com.regnosys.rosetta.rosetta.simple.Operation import com.regnosys.rosetta.rosetta.simple.Segment import com.regnosys.rosetta.rosetta.simple.ShortcutDeclaration +import com.regnosys.rosetta.rosetta.translate.TranslateMetaInstruction +import com.regnosys.rosetta.rosetta.translate.Translation +import com.regnosys.rosetta.rosetta.translate.TranslationRule +import com.regnosys.rosetta.types.RDataType +import com.regnosys.rosetta.types.RType import com.regnosys.rosetta.types.RosettaTypeProvider -import org.slf4j.Logger -import org.slf4j.LoggerFactory +import com.regnosys.rosetta.types.TypeSystem +import com.regnosys.rosetta.utils.DeepFeatureCallUtil +import com.regnosys.rosetta.utils.RosettaConfigExtension +import java.util.List +import javax.inject.Inject import org.eclipse.emf.ecore.EObject import org.eclipse.emf.ecore.EReference import org.eclipse.xtext.EcoreUtil2 +import org.eclipse.xtext.naming.QualifiedName +import org.eclipse.xtext.resource.EObjectDescription import org.eclipse.xtext.resource.IEObjectDescription +import org.eclipse.xtext.resource.impl.AliasedEObjectDescription import org.eclipse.xtext.scoping.IScope import org.eclipse.xtext.scoping.Scopes import org.eclipse.xtext.scoping.impl.FilteringScope +import org.eclipse.xtext.scoping.impl.ImportNormalizer import org.eclipse.xtext.scoping.impl.ImportedNamespaceAwareLocalScopeProvider +import org.eclipse.xtext.scoping.impl.SimpleScope +import org.eclipse.xtext.util.Strings +import org.slf4j.Logger +import org.slf4j.LoggerFactory import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.* -import static com.regnosys.rosetta.rosetta.simple.SimplePackage.Literals.* import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.* -import com.regnosys.rosetta.rosetta.expression.InlineFunction -import com.regnosys.rosetta.rosetta.RosettaAttributeReference -import java.util.List -import org.eclipse.xtext.scoping.impl.SimpleScope -import org.eclipse.xtext.resource.EObjectDescription -import org.eclipse.xtext.naming.QualifiedName -import com.regnosys.rosetta.utils.RosettaConfigExtension -import org.eclipse.xtext.resource.impl.AliasedEObjectDescription -import com.regnosys.rosetta.rosetta.simple.Attribute -import com.regnosys.rosetta.rosetta.expression.RosettaSymbolReference -import com.regnosys.rosetta.rosetta.expression.ChoiceOperation -import com.regnosys.rosetta.types.RType -import com.regnosys.rosetta.rosetta.RosettaTypeAlias -import com.regnosys.rosetta.rosetta.TypeCall -import com.regnosys.rosetta.rosetta.ParametrizedRosettaType -import javax.inject.Inject -import com.regnosys.rosetta.rosetta.expression.RosettaConstructorExpression -import com.regnosys.rosetta.rosetta.expression.ConstructorKeyValuePair -import com.regnosys.rosetta.rosetta.expression.RosettaDeepFeatureCall -import com.regnosys.rosetta.types.RDataType -import com.regnosys.rosetta.utils.DeepFeatureCallUtil +import static com.regnosys.rosetta.rosetta.simple.SimplePackage.Literals.* +import static com.regnosys.rosetta.rosetta.translate.TranslatePackage.Literals.* /** * This class contains custom scoping description. @@ -72,6 +80,7 @@ class RosettaScopeProvider extends ImportedNamespaceAwareLocalScopeProvider { static Logger LOGGER = LoggerFactory.getLogger(RosettaScopeProvider) @Inject RosettaTypeProvider typeProvider + @Inject TypeSystem typeSystem @Inject extension RosettaExtensions @Inject extension RosettaConfigExtension configs @Inject extension RosettaFunctionExtensions @@ -206,7 +215,7 @@ class RosettaScopeProvider extends ImportedNamespaceAwareLocalScopeProvider { return Scopes.scopeFor(classRef.allAttributes) } return IScope.NULLSCOPE - } + } case ROSETTA_EXTERNAL_ENUM_VALUE__ENUM_REF: { if (context instanceof RosettaExternalEnumValue) { val enumRef = (context.eContainer as RosettaExternalEnum).typeRef @@ -231,6 +240,27 @@ class RosettaScopeProvider extends ImportedNamespaceAwareLocalScopeProvider { case ROSETTA_EXTERNAL_RULE_SOURCE__SUPER_SOURCES: { return defaultScope(context, reference).filteredScope[it.EClass == ROSETTA_EXTERNAL_RULE_SOURCE] } + case TRANSLATION_RULE__ATTRIBUTE: { + if (context instanceof TranslationRule) { + val translation = context.translation + return Scopes.scopeFor(typeSystem.typeCallToRType(translation.resultType).allFeatures(context)) + } + return IScope.NULLSCOPE + } + case TRANSLATE_META_INSTRUCTION__META_FEATURE: { + if (context instanceof TranslateMetaInstruction) { + val container = context.eContainer + val annotated = if (container instanceof TranslationRule) { + container.attribute + } else if (container instanceof Translation) { + container.resultType.type + } + if (annotated instanceof Annotated) { + return new SimpleScope(getMetaDescriptions(annotated)) + } + } + return IScope.NULLSCOPE + } } // LOGGER.warn('''No scope defined for «context.class.simpleName» referencing «reference.name».''') return defaultScope(context, reference) @@ -251,15 +281,47 @@ class RosettaScopeProvider extends ImportedNamespaceAwareLocalScopeProvider { override protected internalGetImportedNamespaceResolvers(EObject context, boolean ignoreCase) { return if (context instanceof RosettaModel) { - val imports = super.internalGetImportedNamespaceResolvers(context, ignoreCase) - imports.add( - doCreateImportNormalizer(getQualifiedNameConverter.toQualifiedName(context.name), true, ignoreCase) - ) - return imports + val List imports = newArrayList(context.imports.map [createImportedNamespaceResolver(importedNamespace, namespaceAlias, ignoreCase)]) + //This import allows two models with the same namespace to reference each other + imports.add(doCreateImportNormalizer(getQualifiedNameConverter.toQualifiedName(context.name), true, ignoreCase)) + return imports } else emptyList } + private def ImportNormalizer createImportedNamespaceResolver(String namespace, String namespaceAlias, + boolean ignoreCase) { + if (Strings.isEmpty(namespace)) { + return null; + } + + val importedNamespace = qualifiedNameConverter.toQualifiedName(namespace) + if (importedNamespace === null || importedNamespace.isEmpty()) { + return null; + } + + val hasWildCard = ignoreCase ? + importedNamespace.getLastSegment().equalsIgnoreCase(getWildCard()) : + importedNamespace.getLastSegment().equals(getWildCard()); + + if (hasWildCard) { + if (importedNamespace.getSegmentCount() <= 1) + return null; + return doCreateImportNormalizer(importedNamespace.skipLast(1), namespaceAlias, true, ignoreCase); + } else { + return doCreateImportNormalizer(importedNamespace, namespaceAlias, false, ignoreCase); + } + } + + private def ImportNormalizer doCreateImportNormalizer(QualifiedName importedNamespace, String namespaceAlias, boolean wildcard, boolean ignoreCase) { + if (namespaceAlias === null) { + return doCreateImportNormalizer(importedNamespace, wildcard, ignoreCase); + } + return new AliasAwareImportNormalizer(importedNamespace, namespaceAlias, wildcard, ignoreCase); + } + + + private def IScope defaultScope(EObject object, EReference reference) { filteredScope(super.getScope(object, reference), [it.EClass !== FUNCTION_DISPATCH]) } @@ -295,6 +357,9 @@ class RosettaScopeProvider extends ImportedNamespaceAwareLocalScopeProvider { object.isPostCondition || descr.EObjectOrProxy.eContainingFeature !== FUNCTION__OUTPUT ]) } + Translation: { + Scopes.scopeFor(object.parameters.filter[name !== null], parentScope) + } RosettaModel: filteredScope(defaultScope(object, reference))[ descr | #{DATA, ROSETTA_ENUMERATION, FUNCTION, ROSETTA_EXTERNAL_FUNCTION, ROSETTA_RULE}.contains(descr.EClass) @@ -325,17 +390,23 @@ class RosettaScopeProvider extends ImportedNamespaceAwareLocalScopeProvider { receiver.symbol } if (feature instanceof Attribute) { - val metas = feature.metaAnnotations.map[it.attribute?.name].filterNull.toList - if (metas !== null && !metas.isEmpty) { - allPosibilities.addAll(configs.findMetaTypes(feature).filter[ - metas.contains(it.name.lastSegment.toString) - ].map[new AliasedEObjectDescription(QualifiedName.create(it.name.lastSegment), it)]) - } + allPosibilities.addAll(getMetaDescriptions(feature)) } return new SimpleScope(allPosibilities) } + private def Iterable getMetaDescriptions(Annotated obj) { + val metas = obj.metaAnnotations.map[it.attribute?.name].filterNull.toList + if (!metas.isEmpty) { + configs.findMetaTypes(obj).filter[ + metas.contains(it.name.lastSegment.toString) + ].map[new AliasedEObjectDescription(QualifiedName.create(it.name.lastSegment), it)] + } else { + emptyList + } + } + private def IScope createDeepFeatureScope(RType receiverType) { if (receiverType instanceof RDataType) { return Scopes.scopeFor(receiverType.findDeepFeatures) diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/CardinalityProvider.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/CardinalityProvider.xtend index cf845e1eb..2447fb64f 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/CardinalityProvider.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/CardinalityProvider.xtend @@ -68,8 +68,10 @@ import com.regnosys.rosetta.rosetta.RosettaRule import com.regnosys.rosetta.rosetta.expression.ToDateOperation import com.regnosys.rosetta.rosetta.expression.ToDateTimeOperation import com.regnosys.rosetta.rosetta.expression.ToZonedDateTimeOperation +import com.regnosys.rosetta.rosetta.translate.TranslationParameter import com.regnosys.rosetta.rosetta.expression.RosettaDeepFeatureCall import com.regnosys.rosetta.rosetta.expression.DefaultOperation +import com.regnosys.rosetta.rosetta.expression.TranslateDispatchOperation class CardinalityProvider extends RosettaExpressionSwitch { static Logger LOGGER = LoggerFactory.getLogger(CardinalityProvider) @@ -127,6 +129,9 @@ class CardinalityProvider extends RosettaExpressionSwitch { TypeParameter: { false } + TranslationParameter: { + false + } default: { LOGGER.error("Cardinality not defined for symbol: " + symbol?.eClass?.name) false @@ -152,7 +157,7 @@ class CardinalityProvider extends RosettaExpressionSwitch { } def isImplicitVariableMulti(EObject context, boolean breakOnClosureParameter) { - val definingContainer = context.findContainerDefiningImplicitVariable + val definingContainer = context.findObjectDefiningImplicitVariable definingContainer.map [ if (it instanceof Data) { false @@ -160,6 +165,8 @@ class CardinalityProvider extends RosettaExpressionSwitch { isClosureParameterMulti(it.function) } else if (it instanceof RosettaRule) { false + } else if (it instanceof TranslationParameter) { + false } else { false } @@ -247,7 +254,7 @@ class CardinalityProvider extends RosettaExpressionSwitch { } } else if (op instanceof RosettaImplicitVariable) { - val definingContainer = op.findContainerDefiningImplicitVariable + val definingContainer = op.findObjectDefiningImplicitVariable definingContainer.map [ if (it instanceof ThenOperation) (it as RosettaFunctionalOperation).argument.isOutputListOfLists @@ -531,5 +538,10 @@ class CardinalityProvider extends RosettaExpressionSwitch { override protected caseToZonedDateTimeOperation(ToZonedDateTimeOperation expr, Boolean breakOnClosureParameter) { false - } + } + + override protected caseTranslateDispatchOperation(TranslateDispatchOperation expr, Boolean context) { + expr.inputs.head.isMulti(context) + } + } \ No newline at end of file diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RAssignedRoot.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RAssignedRoot.java index 96efc7127..d373e93b9 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RAssignedRoot.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RAssignedRoot.java @@ -18,4 +18,5 @@ public interface RAssignedRoot { String getName(); + boolean isMulti(); } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RAttribute.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RAttribute.java index 6668b8a34..29c1c9667 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RAttribute.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RAttribute.java @@ -43,6 +43,7 @@ public RType getRType() { return rType; } + @Override public boolean isMulti() { return isMulti; } @@ -75,6 +76,9 @@ public boolean equals(Object obj) { && Objects.equals(rType, other.rType); } - + @Override + public String toString() { + return String.format("RAttribute[name=%s, type=%s, isMulti=%s]", name, rType, isMulti); + } } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RFunction.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RFunction.java index 9ef4e373d..2bce1bcfa 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RFunction.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RFunction.java @@ -23,11 +23,13 @@ import com.rosetta.model.lib.ModelId; import com.rosetta.model.lib.ModelReportId; import com.rosetta.model.lib.ModelSymbolId; +import com.rosetta.model.lib.ModelTranslationId; import com.rosetta.util.DottedPath; public class RFunction { private ModelSymbolId symbolId; private ModelReportId reportId; + private ModelTranslationId translationId; private String definition; private List inputs; private RAttribute output; @@ -66,12 +68,22 @@ public RFunction(ModelReportId reportId, String definition, List inp shortcuts, operations, annotations); this.reportId = reportId; } + public RFunction(ModelTranslationId translationId, String definition, List inputs, + RAttribute output, RFunctionOrigin origin, List preConditions, List postConditions, + List shortcuts, List operations, List annotations) { + this(definition, inputs, output, origin, preConditions, postConditions, + shortcuts, operations, annotations); + this.translationId = translationId; + } public ModelId getId() { if (symbolId != null) { return symbolId; } - return reportId; + if (reportId != null) { + return reportId; + } + return translationId; } public ModelSymbolId getSymbolId() { @@ -82,6 +94,10 @@ public ModelReportId getReportId() { return reportId; } + public ModelTranslationId getTranslationId() { + return translationId; + } + public DottedPath getNamespace() { return getId().getNamespace(); } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RFunctionOrigin.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RFunctionOrigin.java index c0ab57baf..a8f7e4c02 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RFunctionOrigin.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RFunctionOrigin.java @@ -17,5 +17,5 @@ package com.regnosys.rosetta.types; public enum RFunctionOrigin { - FUNCTION,RULE,REPORT + FUNCTION,RULE,REPORT,TRANSLATION } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RObjectFactory.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RObjectFactory.java index 497cbfdf8..0f4149cb0 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RObjectFactory.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RObjectFactory.java @@ -17,6 +17,7 @@ package com.regnosys.rosetta.types; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -25,32 +26,43 @@ import org.eclipse.xtext.EcoreUtil2; +import com.google.common.collect.Streams; import com.regnosys.rosetta.RosettaExtensions; import com.regnosys.rosetta.rosetta.RosettaCardinality; import com.regnosys.rosetta.rosetta.RosettaFactory; import com.regnosys.rosetta.rosetta.RosettaReport; import com.regnosys.rosetta.rosetta.RosettaRule; +import com.regnosys.rosetta.rosetta.TypeCall; import com.regnosys.rosetta.rosetta.expression.ExpressionFactory; import com.regnosys.rosetta.rosetta.expression.RosettaSymbolReference; +import com.regnosys.rosetta.rosetta.expression.TranslateDispatchOperation; import com.regnosys.rosetta.rosetta.simple.Attribute; import com.regnosys.rosetta.rosetta.simple.Data; import com.regnosys.rosetta.rosetta.simple.Function; import com.regnosys.rosetta.rosetta.simple.Operation; import com.regnosys.rosetta.rosetta.simple.ShortcutDeclaration; import com.regnosys.rosetta.rosetta.simple.SimpleFactory; +import com.regnosys.rosetta.rosetta.translate.TranslateInstruction; +import com.regnosys.rosetta.rosetta.translate.TranslateMetaInstruction; +import com.regnosys.rosetta.rosetta.translate.TranslateSource; +import com.regnosys.rosetta.rosetta.translate.Translation; +import com.regnosys.rosetta.rosetta.translate.TranslationParameter; +import com.regnosys.rosetta.utils.TranslateUtil; import com.rosetta.model.lib.ModelReportId; import com.rosetta.model.lib.ModelSymbolId; import com.rosetta.util.DottedPath; public class RObjectFactory { @Inject - private RosettaTypeProvider rosettaTypeProvider; + private RosettaTypeProvider typeProvider; @Inject private CardinalityProvider cardinalityProvider; @Inject private TypeSystem typeSystem; @Inject private RosettaExtensions rosettaExtensions; + @Inject + private TranslateUtil translateUtil; public RFunction buildRFunction(Function function) { return new RFunction( @@ -67,7 +79,7 @@ public RFunction buildRFunction(Function function) { public RFunction buildRFunction(RosettaRule rule) { RType inputRType = typeSystem.typeCallToRType(rule.getInput()); - RType outputRType = rosettaTypeProvider.getRType(rule.getExpression()); + RType outputRType = typeProvider.getRType(rule.getExpression()); boolean outputIsMulti = cardinalityProvider.isMulti(rule.getExpression()); RAttribute outputAttribute = new RAttribute("output", null, outputRType, List.of(), outputIsMulti); @@ -155,15 +167,83 @@ private ROperation generateOperationForRuleReference(Attribute inputAttribute, R inputAttributeSymbolRef.setSymbol(inputAttribute); RosettaSymbolReference symbolRef = ExpressionFactory.eINSTANCE.createRosettaSymbolReference(); + symbolRef.setGenerated(true); symbolRef.setSymbol(rule); symbolRef.setExplicitArguments(true); symbolRef.getArgs().add(inputAttributeSymbolRef); return new ROperation(ROperationType.SET, pathHead, pathTail, symbolRef); } + + public RFunction buildRFunction(Translation translation) { + List inputs = translation.getParameters().stream().map(this::buildRAttribute).collect(Collectors.toList()); + RType outputRType = typeSystem.typeCallToRType(translation.getResultType()); + RAttribute outputAttribute = new RAttribute("output", null, outputRType, List.of(), false); + List operations = + Streams.concat( + // type level instructions + translation.getTypeInstructions() + .stream() + .map(instr -> generateOperationForTranslateInstruction(instr, translation.getSource(), translation.getResultType(), outputAttribute, List.of(), false)), + // type level meta instructions + translation.getTypeMetaInstructions() + .stream() + .map(instr -> generateOperationForTranslateMetaInstruction(instr, translation.getSource(), instr.getMetaFeature().getTypeCall(), outputAttribute, List.of(), false)), + // attribute level instructions + translation.getRules() + .stream() + .flatMap(rule -> { + RAttribute attr = buildRAttribute(rule.getAttribute()); + return Streams.concat( + rule.getInstructions() + .stream() + .map(instr -> generateOperationForTranslateInstruction(instr, translation.getSource(), rule.getAttribute().getTypeCall(), outputAttribute, List.of(attr), attr.isMulti())), + // attribute level meta instructions + rule.getMetaInstructions() + .stream() + .map(instr -> generateOperationForTranslateMetaInstruction(instr, translation.getSource(), instr.getMetaFeature().getTypeCall(), outputAttribute, List.of(attr), attr.isMulti())) + ); + }) + ).collect(Collectors.toList()); + + return new RFunction( + translateUtil.toTranslationId(translation), + null, + inputs, + outputAttribute, + RFunctionOrigin.TRANSLATION, + List.of(), + List.of(), + List.of(), + operations, + List.of() + ); + } + + public ROperation generateOperationForTranslateInstruction(TranslateInstruction instr, TranslateSource source, TypeCall outputType, RAttribute outputAttribute, List assignPath, boolean isMulti) { + TranslateDispatchOperation op = ExpressionFactory.eINSTANCE.createTranslateDispatchOperation(); + op.setGenerated(true); + op.setOutputType(outputType); + op.setSource(source); + op.getInputs().addAll(instr.getExpressions()); + instr.set_internalDispatchExpression(op); + return new ROperation(isMulti ? ROperationType.ADD : ROperationType.SET, outputAttribute, assignPath, op); + } + + public ROperation generateOperationForTranslateMetaInstruction(TranslateMetaInstruction instr, TranslateSource source, TypeCall outputType, RAttribute outputAttribute, List assignPath, boolean isMulti) { + TranslateDispatchOperation op = ExpressionFactory.eINSTANCE.createTranslateDispatchOperation(); + op.setGenerated(true); + op.setOutputType(outputType); + op.setSource(source); + op.getInputs().addAll(instr.getExpressions()); + instr.set_internalDispatchExpression(op); + RType metaRType = typeSystem.typeCallToRType(instr.getMetaFeature().getTypeCall()); + RAttribute metaFeature = new RAttribute(instr.getMetaFeature().getName(), null, metaRType, List.of(), false); + return new ROperation(isMulti ? ROperationType.ADD : ROperationType.SET, outputAttribute, assignPath, metaFeature, op); + } public RAttribute buildRAttribute(Attribute attribute) { - RType rType = this.rosettaTypeProvider.getRTypeOfSymbol(attribute); + RType rType = typeProvider.getRTypeOfSymbol(attribute); List metaAnnotations = attribute.getAnnotations().stream() .filter(a -> a.getAnnotation().getName().equals("metadata")).map(a -> buildRAttribute(a.getAttribute())) .collect(Collectors.toList()); @@ -172,9 +252,19 @@ public RAttribute buildRAttribute(Attribute attribute) { cardinalityProvider.isSymbolMulti(attribute)); } + + public RAttribute buildRAttribute(TranslationParameter parameter) { + RType rType = typeProvider.getRTypeOfSymbol(parameter); + String name = parameter.getName(); + if (name == null) { + name = "item"; + } + return new RAttribute(name, null, rType, Collections.emptyList(), false); + + } public RShortcut buildRShortcut(ShortcutDeclaration shortcut) { - return new RShortcut(shortcut.getName(), shortcut.getDefinition(), shortcut.getExpression()); + return new RShortcut(shortcut.getName(), cardinalityProvider.isSymbolMulti(shortcut), shortcut.getDefinition(), shortcut.getExpression()); } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/ROperation.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/ROperation.java index 547aef7d1..5cd810372 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/ROperation.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/ROperation.java @@ -22,16 +22,26 @@ import com.regnosys.rosetta.rosetta.expression.RosettaExpression; public class ROperation { - private ROperationType rOperationType; - private RAssignedRoot pathHead; - private List pathTail; - private RosettaExpression expression; + private final ROperationType rOperationType; + private final RAssignedRoot pathHead; + private final List pathTail; + private final RosettaExpression expression; + private final RAttribute metaFeature; public ROperation(ROperationType rOperationType, RAssignedRoot pathHead, List pathTail, RosettaExpression expression) { this.rOperationType = rOperationType; this.pathHead = pathHead; this.pathTail = pathTail; this.expression = expression; + this.metaFeature = null; + } + + public ROperation(ROperationType rOperationType, RAssignedRoot pathHead, List pathTail, RAttribute metaFeature, RosettaExpression expression) { + this.rOperationType = rOperationType; + this.pathHead = pathHead; + this.pathTail = pathTail; + this.expression = expression; + this.metaFeature = metaFeature; } public ROperationType getROperationType() { @@ -49,10 +59,18 @@ public List getPathTail() { public RosettaExpression getExpression() { return expression; } + + public boolean isMetaOperation() { + return metaFeature != null; + } + + public RAttribute getMetaFeature() { + return metaFeature; + } @Override public int hashCode() { - return Objects.hash(expression, pathHead, pathTail, rOperationType); + return Objects.hash(expression, metaFeature, pathHead, pathTail, rOperationType); } @Override @@ -64,9 +82,10 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; ROperation other = (ROperation) obj; - return Objects.equals(expression, other.expression) && Objects.equals(pathHead, other.pathHead) - && Objects.equals(pathTail, other.pathTail) && rOperationType == other.rOperationType; + return Objects.equals(expression, other.expression) && Objects.equals(metaFeature, other.metaFeature) + && Objects.equals(pathHead, other.pathHead) && Objects.equals(pathTail, other.pathTail) + && rOperationType == other.rOperationType; } - + } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RShortcut.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RShortcut.java index 19ec65fa4..a9bd8791a 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RShortcut.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RShortcut.java @@ -22,11 +22,13 @@ public class RShortcut implements RAssignedRoot { private String name; + private boolean isMulti; private String definition; private RosettaExpression expression; - public RShortcut(String name, String definition, RosettaExpression expression) { + public RShortcut(String name, boolean isMulti, String definition, RosettaExpression expression) { this.name = name; + this.isMulti = isMulti; this.definition = definition; this.expression = expression; } @@ -35,6 +37,11 @@ public RShortcut(String name, String definition, RosettaExpression expression) { public String getName() { return name; } + + @Override + public boolean isMulti() { + return isMulti; + } public String getDefinition() { return definition; @@ -60,5 +67,5 @@ public boolean equals(Object obj) { RShortcut other = (RShortcut) obj; return Objects.equals(definition, other.definition) && Objects.equals(expression, other.expression) && Objects.equals(name, other.name); - } + } } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/Rosetta.xsemantics b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/Rosetta.xsemantics index 1858515be..e3391e38a 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/Rosetta.xsemantics +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/Rosetta.xsemantics @@ -67,6 +67,7 @@ import com.regnosys.rosetta.interpreter.RosettaValue import com.rosetta.util.DottedPath import com.regnosys.rosetta.rosetta.RosettaRule import java.math.BigInteger +import com.regnosys.rosetta.rosetta.translate.TranslationParameter inject extension TypeFactory typeFactory inject extension TypeValidationUtil util @@ -160,7 +161,7 @@ auxiliary functionalOperationItemType(RosettaFunctionalOperation op) { } auxiliary typeOfImplicitVariable(EObject c) { - val definingContainer = c.findContainerDefiningImplicitVariable + val definingContainer = c.findObjectDefiningImplicitVariable definingContainer.map [ if (it instanceof Data) { createListType(new RDataType(it), single) @@ -168,7 +169,9 @@ auxiliary typeOfImplicitVariable(EObject c) { functionalOperationItemType } else if (it instanceof RosettaRule) { input?.typeCallToRType(new RosettaInterpreterContext)?.createListType(single) - } + } else if (it instanceof TranslationParameter) { + typeCall.typeCallToRType(new RosettaInterpreterContext)?.createListType(single) + } ] } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RosettaTypeProvider.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RosettaTypeProvider.xtend index 28ac18d34..291c9afa3 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RosettaTypeProvider.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/types/RosettaTypeProvider.xtend @@ -76,8 +76,10 @@ import javax.inject.Provider import com.regnosys.rosetta.rosetta.expression.ToDateOperation import com.regnosys.rosetta.rosetta.expression.ToDateTimeOperation import com.regnosys.rosetta.rosetta.expression.ToZonedDateTimeOperation +import com.regnosys.rosetta.rosetta.translate.TranslationParameter import com.regnosys.rosetta.rosetta.expression.RosettaDeepFeatureCall import com.regnosys.rosetta.rosetta.expression.DefaultOperation +import com.regnosys.rosetta.rosetta.expression.TranslateDispatchOperation class RosettaTypeProvider extends RosettaExpressionSwitch> { public static String EXPRESSION_RTYPE_CACHE_KEY = RosettaTypeProvider.canonicalName + ".EXPRESSION_RTYPE" @@ -159,6 +161,9 @@ class RosettaTypeProvider extends RosettaExpressionSwitch cycleTracker) { @@ -219,14 +224,16 @@ class RosettaTypeProvider extends RosettaExpressionSwitch cycleTracker) { - val definingContainer = context.findContainerDefiningImplicitVariable + val definingContainer = context.findObjectDefiningImplicitVariable definingContainer.map [ if (it instanceof Data) { new RDataType(it) } else if (it instanceof RosettaFunctionalOperation) { safeRType(argument, cycleTracker) } else if (it instanceof RosettaRule) { - input?.typeCallToRType ?: MISSING + input?.typeCallToRType + } else if (it instanceof TranslationParameter) { + typeCall.typeCallToRType } ].orElse(MISSING) } @@ -513,4 +520,8 @@ class RosettaTypeProvider extends RosettaExpressionSwitch context) { + expr.outputType.typeCallToRType + } + } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/ImplicitVariableUtil.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/ImplicitVariableUtil.java index 1b890106b..2af1a7cb5 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/ImplicitVariableUtil.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/ImplicitVariableUtil.java @@ -16,12 +16,15 @@ package com.regnosys.rosetta.utils; +import java.util.List; import java.util.Optional; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.EcoreUtil2; import com.regnosys.rosetta.rosetta.RosettaRule; +import com.regnosys.rosetta.rosetta.translate.Translation; +import com.regnosys.rosetta.rosetta.translate.TranslationParameter; import com.regnosys.rosetta.rosetta.expression.ExpressionFactory; import com.regnosys.rosetta.rosetta.expression.InlineFunction; import com.regnosys.rosetta.rosetta.expression.RosettaFunctionalOperation; @@ -44,7 +47,7 @@ public RosettaImplicitVariable getDefaultImplicitVariable() { /** * Find the enclosing object that defines the implicit variable in the given expression. */ - public Optional findContainerDefiningImplicitVariable(EObject context) { + public Optional findObjectDefiningImplicitVariable(EObject context) { Iterable containers = EcoreUtil2.getAllContainers(context); EObject prev = context; for (EObject container: containers) { @@ -58,16 +61,22 @@ public Optional findContainerDefiningImplicitVariable(EObject context) } } else if (container instanceof RosettaRule) { return Optional.of(container); + } else if (container instanceof Translation) { + Translation trans = (Translation)container; + return findFirstUnnamedParameter(trans.getParameters()); } prev = container; } return Optional.empty(); } + private Optional findFirstUnnamedParameter(List params) { + return params.stream().filter(p -> p.getName() == null).findFirst(); + } /** * Indicates whether an implicit variable exists in the given context. */ public boolean implicitVariableExistsInContext(EObject context) { - return findContainerDefiningImplicitVariable(context).isPresent(); + return findObjectDefiningImplicitVariable(context).isPresent(); } } diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/RosettaExpressionSwitch.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/RosettaExpressionSwitch.java index b2b7e0e18..5201a8503 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/RosettaExpressionSwitch.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/RosettaExpressionSwitch.java @@ -72,6 +72,7 @@ import com.regnosys.rosetta.rosetta.expression.ToStringOperation; import com.regnosys.rosetta.rosetta.expression.ToTimeOperation; import com.regnosys.rosetta.rosetta.expression.ToZonedDateTimeOperation; +import com.regnosys.rosetta.rosetta.expression.TranslateDispatchOperation; public abstract class RosettaExpressionSwitch { @@ -94,6 +95,8 @@ protected Return doSwitch(RosettaExpression expr, Context context) { return doSwitch((RosettaReference)expr, context); } else if (expr instanceof RosettaOperation) { return doSwitch((RosettaOperation)expr, context); + } else if (expr instanceof TranslateDispatchOperation) { + return caseTranslateDispatchOperation((TranslateDispatchOperation)expr, context); } throw errorMissedCase(expr); } @@ -281,6 +284,8 @@ private UnsupportedOperationException errorMissedCase(RosettaExpression expr) { protected abstract Return caseImplicitVariable(RosettaImplicitVariable expr, Context context); protected abstract Return caseSymbolReference(RosettaSymbolReference expr, Context context); + protected abstract Return caseTranslateDispatchOperation(TranslateDispatchOperation expr, Context context); + protected abstract Return caseAddOperation(ArithmeticOperation expr, Context context); protected abstract Return caseSubtractOperation(ArithmeticOperation expr, Context context); protected abstract Return caseMultiplyOperation(ArithmeticOperation expr, Context context); diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/TranslateUtil.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/TranslateUtil.java new file mode 100644 index 000000000..5639c73ee --- /dev/null +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/utils/TranslateUtil.java @@ -0,0 +1,81 @@ +package com.regnosys.rosetta.utils; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.inject.Inject; + +import com.google.common.collect.Streams; +import com.regnosys.rosetta.rosetta.RosettaType; +import com.regnosys.rosetta.rosetta.translate.TranslateSource; +import com.regnosys.rosetta.rosetta.translate.Translation; +import com.regnosys.rosetta.types.RType; +import com.regnosys.rosetta.types.RosettaTypeProvider; +import com.regnosys.rosetta.types.TypeSystem; +import com.rosetta.model.lib.ModelSymbolId; +import com.rosetta.model.lib.ModelTranslationId; +import com.rosetta.util.DottedPath; + +public class TranslateUtil { + private final TypeSystem typeSystem; + private final RosettaTypeProvider typeProvider; + + @Inject + public TranslateUtil(TypeSystem typeSystem, RosettaTypeProvider typeProvider) { + this.typeSystem = typeSystem; + this.typeProvider = typeProvider; + } + + private Stream getAllTranslations(TranslateSource source) { + return Streams.concat( + source.getSuperSources().stream().flatMap(s -> getAllTranslations(s)), + source.getTranslations().stream() + ); + } + + public List findMatches(TranslateSource source, RType resultType, List inputTypes) { + return getAllTranslations(source) + .filter(t -> matches(t, resultType, inputTypes)) + .collect(Collectors.toList()); + } + + public boolean hasAnyMatch(TranslateSource source, RType resultType, List inputTypes) { + return getAllTranslations(source) + .anyMatch(t -> matches(t, resultType, inputTypes)); + } + + public boolean matches(Translation translation, RType resultType, List inputTypes) { + // Check parameters match + if (translation.getParameters().size() != inputTypes.size()) { + return false; + } + for (int i=0;i toModelSymbolId(p.getTypeCall().getType())).collect(Collectors.toList()), + toModelSymbolId(translation.getResultType().getType()) + ); + } + private ModelSymbolId toModelSymbolId(RosettaType t) { + return new ModelSymbolId(DottedPath.splitOnDots(t.getModel().getName()), t.getName()); + } +} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/AbstractDeclarativeRosettaValidator.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/AbstractDeclarativeRosettaValidator.java new file mode 100644 index 000000000..c4b36323b --- /dev/null +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/AbstractDeclarativeRosettaValidator.java @@ -0,0 +1,108 @@ +package com.regnosys.rosetta.validation; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.emf.common.util.Diagnostic; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.xtext.Action; +import org.eclipse.xtext.Assignment; +import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.nodemodel.ICompositeNode; +import org.eclipse.xtext.nodemodel.INode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.validation.AbstractDeclarativeValidator; +import org.eclipse.xtext.validation.EValidatorRegistrar; +import org.eclipse.xtext.validation.FeatureBasedDiagnostic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractDeclarativeRosettaValidator extends AbstractDeclarativeValidator { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDeclarativeRosettaValidator.class); + + @Override + protected List getEPackages() { + List result = new ArrayList(); + result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/Rosetta")); + result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/RosettaSimple")); + result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/RosettaExpression")); + result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/RosettaTranslate")); + return result; + } + + @Override + public void register(EValidatorRegistrar registrar) { + } + + @Override + protected void handleExceptionDuringValidation(Throwable targetException) throws RuntimeException { + super.handleExceptionDuringValidation(targetException); + LOGGER.error(targetException.getMessage(), targetException); + } + + @Override + protected MethodWrapper createMethodWrapper(AbstractDeclarativeValidator instanceToUse, Method method) { + return new RosettaMethodWrapper(instanceToUse, method); + } + + private static class RosettaMethodWrapper extends MethodWrapper { + protected RosettaMethodWrapper(AbstractDeclarativeValidator instance, Method m) { + super(instance, m); + } + + @Override + public void invoke(State state) { + try { + super.invoke(state); + } catch (Exception e) { + String message = "Unexpected validation failure running " + getMethod().getName(); + LOGGER.error(message, e); + state.hasErrors = true; + state.chain.add(createDiagnostic(message, state)); + } + } + + private Diagnostic createDiagnostic(String message, State state) { + return new FeatureBasedDiagnostic(Diagnostic.ERROR, message, state.currentObject, null, -1, state.currentCheckType, null); + } + } + + protected void errorKeyword(String message, EObject o, Keyword keyword) { + INode k = findDirectKeyword(o, keyword); + if (k != null) { + getMessageAcceptor().acceptError( + message, + o, + k.getOffset(), + k.getLength(), + null + ); + } + } + protected INode findDirectKeyword(EObject o, Keyword keyword) { + return findDirectKeyword(o, keyword.getValue()); + } + protected INode findDirectKeyword(EObject o, String keyword) { + ICompositeNode node = NodeModelUtils.findActualNodeFor(o); + return findDirectKeyword(node, keyword); + } + protected INode findDirectKeyword(ICompositeNode node, String keyword) { + for (INode n : node.getChildren()) { + EObject ge = n.getGrammarElement(); + if (ge instanceof Keyword && ((Keyword)ge).getValue().equals(keyword)) { // I compare the keywords by value instead of directly by reference because of an issue that sometimes arises when running multiple tests consecutively. I'm not sure what the issue is. + return n; + } + if ((ge instanceof RuleCall || ge instanceof Action) && n instanceof ICompositeNode && !(ge.eContainer() instanceof Assignment)) { + INode keywordInFragment = findDirectKeyword((ICompositeNode)n, keyword); + if (keywordInFragment != null) { + return keywordInFragment; + } + } + } + return null; + } +} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend index 34af66a1b..c014c1031 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaSimpleValidator.xtend @@ -71,7 +71,6 @@ import com.regnosys.rosetta.utils.ExternalAnnotationUtil import com.regnosys.rosetta.utils.ExternalAnnotationUtil.CollectRuleVisitor import com.regnosys.rosetta.utils.ImplicitVariableUtil import com.regnosys.rosetta.utils.RosettaConfigExtension -import java.lang.reflect.Method import java.time.format.DateTimeFormatter import java.util.List import java.util.Map @@ -79,23 +78,15 @@ import java.util.Set import java.util.Stack import java.util.regex.Pattern import java.util.regex.PatternSyntaxException -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.eclipse.emf.common.util.Diagnostic import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EPackage import org.eclipse.emf.ecore.EReference import org.eclipse.xtext.EcoreUtil2 -import org.eclipse.xtext.Keyword import org.eclipse.xtext.naming.IQualifiedNameProvider import org.eclipse.xtext.naming.QualifiedName import org.eclipse.xtext.nodemodel.util.NodeModelUtils import org.eclipse.xtext.resource.XtextSyntaxDiagnostic import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider -import org.eclipse.xtext.validation.AbstractDeclarativeValidator import org.eclipse.xtext.validation.Check -import org.eclipse.xtext.validation.EValidatorRegistrar -import org.eclipse.xtext.validation.FeatureBasedDiagnostic import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.* import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.* @@ -113,13 +104,9 @@ import com.regnosys.rosetta.types.RDataType import com.regnosys.rosetta.rosetta.ParametrizedRosettaType import com.regnosys.rosetta.rosetta.RosettaRootElement import com.regnosys.rosetta.rosetta.expression.ThenOperation -import org.eclipse.xtext.RuleCall import org.eclipse.xtext.nodemodel.INode -import org.eclipse.xtext.nodemodel.ICompositeNode -import org.eclipse.xtext.Assignment import com.regnosys.rosetta.rosetta.expression.RosettaOperation import com.regnosys.rosetta.types.CardinalityProvider -import org.eclipse.xtext.Action import com.regnosys.rosetta.rosetta.expression.ParseOperation import com.regnosys.rosetta.rosetta.expression.ToStringOperation import com.regnosys.rosetta.types.builtin.RBasicType @@ -136,7 +123,7 @@ import com.regnosys.rosetta.rosetta.expression.DefaultOperation // TODO: split expression validator // TODO: type check type call arguments -class RosettaSimpleValidator extends AbstractDeclarativeValidator { +class RosettaSimpleValidator extends AbstractDeclarativeRosettaValidator { @Inject extension RosettaExtensions @Inject extension RosettaExpectedTypeProvider @@ -154,84 +141,6 @@ class RosettaSimpleValidator extends AbstractDeclarativeValidator { @Inject extension TypeSystem @Inject extension RosettaGrammarAccess - static final Logger log = LoggerFactory.getLogger(RosettaSimpleValidator); - - protected override List getEPackages() { - val result = newArrayList - result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/Rosetta")); - result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/RosettaSimple")); - result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/RosettaExpression")); - return result; - } - - override void register(EValidatorRegistrar registrar) { - } - - protected override void handleExceptionDuringValidation(Throwable targetException) throws RuntimeException { - super.handleExceptionDuringValidation(targetException); - log.error(targetException.getMessage(), targetException); - } - - protected override MethodWrapper createMethodWrapper(AbstractDeclarativeValidator instanceToUse, Method method) { - return new RosettaMethodWrapper(instanceToUse, method); - } - - protected static class RosettaMethodWrapper extends MethodWrapper { - protected new(AbstractDeclarativeValidator instance, Method m) { - super(instance, m) - } - - override void invoke(State state) { - try { - super.invoke(state); - } catch (Exception e) { - val String message = "Unexpected validation failure running " + method.name - log.error(message, e); - state.hasErrors = true; - state.chain.add(createDiagnostic(message, state)) - } - } - - def Diagnostic createDiagnostic(String message, State state) { - new FeatureBasedDiagnostic(Diagnostic.ERROR, message, state.currentObject, null, -1, state.currentCheckType, - null, null) - } - } - - private def errorKeyword(String message, EObject o, Keyword keyword) { - val k = findDirectKeyword(o, keyword) - if (k !== null) { - messageAcceptor.acceptError( - message, - o, - k.offset, - k.length, - null - ) - } - } - private def INode findDirectKeyword(EObject o, Keyword keyword) { - findDirectKeyword(o, keyword.value) - } - private def INode findDirectKeyword(EObject o, String keyword) { - val node = NodeModelUtils.findActualNodeFor(o) - findDirectKeyword(node, keyword) - } - private def INode findDirectKeyword(ICompositeNode node, String keyword) { - for (n : node.children) { - val ge = n.grammarElement - if (ge instanceof Keyword && (ge as Keyword).value == keyword) { // I compare the keywords by value instead of directly by reference because of an issue that sometimes arises when running multiple tests consecutively. I'm not sure what the issue is. - return n - } - if ((ge instanceof RuleCall || ge instanceof Action) && n instanceof ICompositeNode && !(ge.eContainer instanceof Assignment)) { - val keywordInFragment = findDirectKeyword(n as ICompositeNode, keyword) - if (keywordInFragment !== null) { - return keywordInFragment - } - } - } - } - @Check def void ruleMustHaveInputTypeDeclared(RosettaRule rule) { if (rule.input === null) { @@ -1502,7 +1411,6 @@ class RosettaSimpleValidator extends AbstractDeclarativeValidator { @Check def checkImport(RosettaModel model) { - var usedNames = model.eAllContents.flatMap[ eCrossReferences.filter(RosettaRootElement).filter[isResolved].iterator ].map[ @@ -1512,7 +1420,13 @@ class RosettaSimpleValidator extends AbstractDeclarativeValidator { for (ns : model.imports) { if (ns.importedNamespace !== null) { val qn = QualifiedName.create(ns.importedNamespace.split('\\.')) - val isUsed = if (qn.lastSegment.equals('*')) { + val isWildcard = qn.lastSegment.equals('*'); + if (!isWildcard && ns.namespaceAlias !== null) { + error('''"as" statement can only be used with wildcard imports''', ns, IMPORT__NAMESPACE_ALIAS); + } + + + val isUsed = if (isWildcard) { usedNames.stream.anyMatch[startsWith(qn.skipLast(1)) && segmentCount === qn.segmentCount] } else { usedNames.contains(qn) diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaTranslateValidator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaTranslateValidator.xtend new file mode 100644 index 000000000..f61249350 --- /dev/null +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaTranslateValidator.xtend @@ -0,0 +1,115 @@ +package com.regnosys.rosetta.validation + +import org.eclipse.xtext.validation.Check +import com.regnosys.rosetta.rosetta.translate.Translation +import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.* +import com.regnosys.rosetta.rosetta.translate.TranslateInstruction +import com.regnosys.rosetta.rosetta.translate.TranslateMetaInstruction +import com.regnosys.rosetta.rosetta.translate.TranslationRule +import javax.inject.Inject +import com.regnosys.rosetta.types.CardinalityProvider +import com.regnosys.rosetta.utils.TranslateUtil +import com.regnosys.rosetta.types.RosettaTypeProvider +import com.regnosys.rosetta.types.TypeSystem +import com.regnosys.rosetta.types.RType +import java.util.List +import com.regnosys.rosetta.rosetta.translate.TranslateSource +import com.regnosys.rosetta.rosetta.RosettaFeature +import com.regnosys.rosetta.rosetta.expression.RosettaExpression +import org.eclipse.xtext.EcoreUtil2 +import com.regnosys.rosetta.rosetta.TypeCall +import com.regnosys.rosetta.rosetta.translate.BaseTranslateInstruction + +class RosettaTranslateValidator extends AbstractDeclarativeRosettaValidator { + + @Inject extension RosettaTypeProvider + @Inject extension TypeSystem + @Inject extension CardinalityProvider + @Inject extension TranslateUtil + + @Check + def void checkUniqueTranslateParameterNames(Translation translation) { + val visited = newHashSet + var unnamedSeen = false + for (param: translation.parameters) { + if (param.name === null) { + if (unnamedSeen) { + error('''Cannot have multiple unnamed parameters.''', param, null); + } + unnamedSeen = true + } else { + if (!visited.add(param.name)) { + error('''Duplicate parameter name `«param.name»`.''', param, ROSETTA_NAMED__NAME); + } + } + } + } + + private def void translateTypeCheck(RType resultType, List inputs, BaseTranslateInstruction instruction) { + val inputTypes = inputs.map[RType] + if (inputs.size !== 1 || !inputTypes.head.isSubtypeOf(resultType)) { + // No direct assignment - check if an appropriate translation exists + val source = EcoreUtil2.getContainerOfType(instruction, TranslateSource) + if (!source.hasAnyMatch(resultType, inputTypes)) { + val multipleInputs = inputTypes.size >= 2 + error('''No translation exists to translate «IF multipleInputs»(«ENDIF»«FOR input : inputTypes SEPARATOR ', '»«input.name»«ENDFOR»«IF multipleInputs»)«ENDIF» into «resultType.name».''', instruction, null); + } + } + } + + private def void translateToFeatureCheck(RosettaFeature feature, boolean isFeatureMulti, List inputs, BaseTranslateInstruction instruction) { + // - For single cardinality attributes, all expressions should be single. + // - For multi cardinality attributes, all expressions should be of the same cardinality. + // - If the translation calls another translation, there should be at least one matching translation in the same translate source. + if (!isFeatureMulti) { + inputs.forEach[ + if (isMulti) { + error('''Expression must be of single cardinality when mapping to attribute `«feature.name»` of single cardinality.''', it, null); + } + ] + } else if (!inputs.empty) { + val firstIsMulti = inputs.head.isMulti + inputs.tail.forEach[ + val exprIsMulti = isMulti + if (exprIsMulti !== firstIsMulti) { + error('''Expression is of «IF exprIsMulti»multi«ELSE»single«ENDIF» cardinality, whereas the first expression is of «IF firstIsMulti»multi«ELSE»single«ENDIF» cardinality.''', it, null); + } + ] + } + + translateTypeCheck(feature.RTypeOfFeature, inputs, instruction) + } + + private def void translateToTypeCheck(TypeCall resultType, List inputs, BaseTranslateInstruction instruction) { + // - All expressions should be singular. + // - If the translation calls another translation, there should be at least one matching translation in the same translate source. + inputs.forEach[ + if (isMulti) { + error('''Expression must be of single cardinality when mapping to a type.''', it, null); + } + ] + + translateTypeCheck(resultType.typeCallToRType, inputs, instruction) + } + + @Check + def void checkTranslateInstruction(TranslateInstruction instruction) { + val container = instruction.eContainer + if (container instanceof TranslationRule) { + translateToFeatureCheck(container.attribute, container.attribute.isFeatureMulti, instruction.expressions, instruction) + } else if (container instanceof Translation) { + translateToTypeCheck(container.resultType, instruction.expressions, instruction) + } + } + + @Check + def void checkTranslateMetaInstruction(TranslateMetaInstruction metaInstruction) { + val container = metaInstruction.eContainer + val isFeatureMulti = if (container instanceof TranslationRule) { + container.attribute.isFeatureMulti + } else { + false + } + translateToFeatureCheck(metaInstruction.metaFeature, isFeatureMulti, metaInstruction.expressions, metaInstruction) + } +} diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaValidator.xtend b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaValidator.xtend index 39de06559..7b4a513c1 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaValidator.xtend +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/RosettaValidator.xtend @@ -10,7 +10,7 @@ import org.eclipse.xtext.validation.ComposedChecks * * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation */ -@ComposedChecks(validators = #[RosettaSimpleValidator, StandaloneRosettaTypingValidator]) +@ComposedChecks(validators = #[RosettaSimpleValidator, StandaloneRosettaTypingValidator, RosettaTranslateValidator]) class RosettaValidator extends AbstractRosettaValidator { diff --git a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/StandaloneRosettaTypingValidator.java b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/StandaloneRosettaTypingValidator.java index 0abaee0e2..5a8442683 100644 --- a/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/StandaloneRosettaTypingValidator.java +++ b/rosetta-lang/src/main/java/com/regnosys/rosetta/validation/StandaloneRosettaTypingValidator.java @@ -76,6 +76,7 @@ protected List getEPackages() { result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/Rosetta")); result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/RosettaSimple")); result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/RosettaExpression")); + result.add(EPackage.Registry.INSTANCE.getEPackage("http://www.rosetta-model.com/RosettaTranslate")); return result; } diff --git a/rosetta-runtime/src/main/java/com/rosetta/model/lib/ModelTranslationId.java b/rosetta-runtime/src/main/java/com/rosetta/model/lib/ModelTranslationId.java new file mode 100644 index 000000000..3ce91f5ea --- /dev/null +++ b/rosetta-runtime/src/main/java/com/rosetta/model/lib/ModelTranslationId.java @@ -0,0 +1,112 @@ +package com.rosetta.model.lib; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.Validate; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.rosetta.util.DottedPath; + +public class ModelTranslationId extends ModelId implements Comparable { + private static Pattern TRAANSLATION_REPR_PATTERN = Pattern.compile("<(?[a-zA-Z0-9_. ]+) -> (?[a-zA-Z0-9_.]+)>"); + + private final ModelSymbolId translateSource; + private final List inputTypes; + private final ModelSymbolId outputType; + + public ModelTranslationId(ModelSymbolId translateSource, List inputTypes, ModelSymbolId outputType) { + super(translateSource.getNamespace()); + Objects.requireNonNull(translateSource); + Validate.noNullElements(inputTypes); + Objects.requireNonNull(outputType); + + this.translateSource = translateSource; + this.inputTypes = inputTypes; + this.outputType = outputType; + } + + @JsonCreator + public static ModelTranslationId fromNamespaceAndTranslationString(String str) { + DottedPath parts = DottedPath.splitOnDots(str); + DottedPath namespaceAndSource = parts.parent(); + ModelSymbolId source = new ModelSymbolId(namespaceAndSource.parent(), namespaceAndSource.last()); + Matcher matcher = TRAANSLATION_REPR_PATTERN.matcher(parts.last()); + if (matcher.matches()) { + String rawInputList = matcher.group("inputList"); + if (rawInputList != null) { + List inputList = Arrays.stream(rawInputList.split(" ")).map(qn -> ModelSymbolId.fromQualifiedName(qn)).collect(Collectors.toList()); + ModelSymbolId output = ModelSymbolId.fromQualifiedName(matcher.group("output")); + return new ModelTranslationId(source, inputList, output); + } + } + throw new IllegalArgumentException("Invalid format for translation representation: " + parts.last()); + } + + public ModelSymbolId getTranslateSource() { + return translateSource; + } + public List getInputTypes() { + return inputTypes; + } + public ModelSymbolId getOutputType() { + return outputType; + } + + @Override + public String getAlphanumericName() { + return inputTypes.stream().map(t -> t.getAlphanumericName()).collect(Collectors.joining("And")) + "To" + outputType.getAlphanumericName(); + } + + @JsonValue + @Override + public String toString() { + return translateSource.getQualifiedName().child("<" + inputTypes.stream().map(t -> t.toString()).collect(Collectors.joining(" ")) + " -> " + outputType + ">").withDots(); + } + + @Override + public int hashCode() { + return Objects.hash(inputTypes, outputType, translateSource); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ModelTranslationId other = (ModelTranslationId) obj; + return Objects.equals(inputTypes, other.inputTypes) && Objects.equals(outputType, other.outputType) + && Objects.equals(translateSource, other.translateSource); + } + + @Override + public int compareTo(ModelTranslationId o) { + int namespaceComp = getNamespace().compareTo(o.getNamespace()); + if (namespaceComp != 0) { + return namespaceComp; + } + int sourceComp = translateSource.compareTo(o.translateSource); + if (sourceComp != 0) { + return sourceComp; + } + for (int i=0; i toJavaFunction(ModelSymbolId id) { return new GeneratedJavaClass<>(packageName, simpleName, new TypeReference>() {}); } + public JavaClass toJavaTranslationFunction(ModelTranslationId translationId) { + DottedPath packageName = translationId.getNamespace().child("translate"); + String inputNames = generateInputName(translationId, false); + String outputName = StringUtils.capitalize(translationId.getOutputType().getName()); + String sourceName = StringUtils.capitalize(translationId.getTranslateSource().getName()); + String simpleName = String.format("Translate%sTo%sUsing%s", inputNames, outputName, sourceName); + //Max Unix file length is 255 chars. A translator called `TranslatorFunc` will result in an inner default file called `TranslatorFunc$TranslatorFuncDefault` + //So our max translator name can be (255 - 7 for default - 4 for ext - 1 for $)/2 = 121.5. So our char limit for function names is 121 + //TOOD: A better solution here would be to stop generating the full name for the default inner class + if (simpleName.length() > 121) { + inputNames = generateInputName(translationId, true); + simpleName = String.format("Translate%sTo%sUsing%s", inputNames, outputName, sourceName); + } + return new GeneratedJavaClass<>(packageName, simpleName, RosettaFunction.class); + } + + private String generateInputName(ModelTranslationId translationId, boolean compress) { + return translationId.getInputTypes().stream() + .map(id -> { + if (compress) { + return id.getName().substring(0, 2); + } else { + return id.getName(); + } + }) + .map(name -> StringUtils.capitalize(name)) + .collect(Collectors.joining("And")); + } + public JavaClass toJavaType(ModelSymbolId id) { DottedPath packageName = id.getNamespace(); String simpleName = id.getName(); diff --git a/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/CodeGeneratorTestHelper.xtend b/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/CodeGeneratorTestHelper.xtend index b6aa51e20..8e2d6d56e 100644 --- a/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/CodeGeneratorTestHelper.xtend +++ b/rosetta-testing/src/main/java/com/regnosys/rosetta/tests/util/CodeGeneratorTestHelper.xtend @@ -108,16 +108,23 @@ class CodeGeneratorTestHelper { } def createInstanceUsingBuilder(Map> classes, RootPackage namespace, String className, Map itemsToSet, Map> itemsToAddToList) { + val clazz = classes.get(namespace + '.' + className) + if (clazz === null) { + throw new RuntimeException('''Class «namespace + '.' + className» not found''') + } val rosettaClassBuilderInstance = classes.get(namespace + '.' + className).getMethod( "builder").invoke(null); itemsToSet.forEach [ name, value | - rosettaClassBuilderInstance.class.getMatchingMethod('set' + name.toFirstUpper, #[value?.class]).invoke( - rosettaClassBuilderInstance, value); + val setter = rosettaClassBuilderInstance.class.getMatchingMethod('set' + name.toFirstUpper, #[value?.class]) + if (setter === null) { + throw new RuntimeException('''No method #«'set' + name.toFirstUpper»(«value?.class?.simpleName») in «rosettaClassBuilderInstance.class»''') + } + setter.invoke(rosettaClassBuilderInstance, value); ] itemsToAddToList.forEach [ name, objectsToAdd | objectsToAdd.forEach [ value | - val clazz = rosettaClassBuilderInstance.class - val meth = getMatchingMethod(clazz, 'add' + name.toFirstUpper, #[value].map[class]) + val builderClazz = rosettaClassBuilderInstance.class + val meth = getMatchingMethod(builderClazz, 'add' + name.toFirstUpper, #[value].map[class]) meth.invoke( rosettaClassBuilderInstance, value); ] diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/translate/TranslateTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/translate/TranslateTest.xtend new file mode 100644 index 000000000..cbbe9052d --- /dev/null +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/translate/TranslateTest.xtend @@ -0,0 +1,260 @@ +package com.regnosys.rosetta.generator.java.translate + +import javax.inject.Inject +import org.eclipse.xtext.testing.InjectWith +import com.regnosys.rosetta.tests.RosettaInjectorProvider +import org.junit.jupiter.api.^extension.ExtendWith +import org.eclipse.xtext.testing.extensions.InjectionExtension +import com.regnosys.rosetta.generator.java.function.FunctionGeneratorHelper +import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper +import org.junit.jupiter.api.Test + +import static org.junit.jupiter.api.Assertions.* +import com.regnosys.rosetta.generator.java.RosettaJavaPackages.RootPackage +import com.rosetta.model.lib.meta.Key +import com.rosetta.model.lib.meta.Reference +import org.junit.jupiter.api.Disabled + +@InjectWith(RosettaInjectorProvider) +@ExtendWith(InjectionExtension) +class TranslateTest { + + @Inject extension FunctionGeneratorHelper + @Inject extension CodeGeneratorTestHelper + @Inject extension TranslateTestUtil + + @Test + def void testSimpleTranslation() { + val code = ''' + type Foo: + a string (1..1) + + type Bar: + b Qux (1..1) + + type Qux: + c string (1..1) + + translate source FooBar { + Foo from Bar: + + a + [from b, 42] + + string from Qux, context number: + [from c + context to-string] + } + '''.generateCode + + val classes = code.compileToClasses + + val bar = classes.createInstanceUsingBuilder("Bar", #{ + "b" -> classes.createInstanceUsingBuilder("Qux", #{ + "c" -> "My favourite number is " + }) + }) + val expectedResult = classes.createInstanceUsingBuilder("Foo", #{ + "a" -> "My favourite number is 42" + }) + + val translation = classes.createTranslation("FooBar", #["Bar"], "Foo"); + assertEquals(expectedResult, translation.invokeFunc(expectedResult.class, #[bar])) + } + + @Test + def void testTranslateClassNameIsLegalLength() { + val code = ''' + type Foo: + a1 string (1..1) + a2 string (1..1) + a3 string (1..1) + a4 string (1..1) + a5 string (1..1) + a6 string (1..1) + + type BarOneWithVeryVeryLongName: + b string (1..1) + + type BarTwoWithVeryVeryLongName: + b string (1..1) + + type BarThreeWithVeryVeryLongName: + b string (1..1) + + type BarFourWithVeryVeryLongName: + b string (1..1) + + type BarFiveWithVeryVeryLongName: + b string (1..1) + + type BarSixWithVeryVeryLongName: + b string (1..1) + + translate source LongTranslate { + Foo from BarOneWithVeryVeryLongName, bar2 BarTwoWithVeryVeryLongName, bar3 BarThreeWithVeryVeryLongName, bar4 BarFourWithVeryVeryLongName, bar5 BarFiveWithVeryVeryLongName: + + a1 + [from b] + + a2 + [from bar2 -> b] + + a3 + [from bar3 -> b] + + a4 + [from bar4 -> b] + + a5 + [from bar5 -> b] + } + '''.generateCode + + val classes = code.compileToClasses + + + val translation = classes.createTranslation("LongTranslate", #["BarOneWithVeryVeryLongName", "BarTwoWithVeryVeryLongName", "BarThreeWithVeryVeryLongName", "BarFourWithVeryVeryLongName", "BarFiveWithVeryVeryLongName"], "Foo"); + + val className = translation.class.canonicalName.replaceAll("^com\\.rosetta\\.test\\.model\\.translate\\.", "") + + assertTrue(className.length + 4 <= 255, "Translator class name too long") + } + + @Test + def void testTranslationWithMultiCardinality() { + val code = ''' + type Foo: + a string (0..*) + + type Bar: + bs Qux (0..*) + + type Qux: + cs string (0..*) + + translate source FooBar { + Foo from Bar: + + a + [from bs, [1, 2]] + [from "Another string"] + + string from Qux, context number: + [from cs join ", " + ": " + context to-string] + } + '''.generateCode + + val classes = code.compileToClasses + + val bar = classes.createInstanceUsingBuilder("Bar", #{ + "bs" -> #[ + classes.createInstanceUsingBuilder("Qux", #{ + "cs" -> #["a", "b"] + }), + classes.createInstanceUsingBuilder("Qux", #{ + "cs" -> #[] + }), + classes.createInstanceUsingBuilder("Qux", #{ + "cs" -> #["This", "is", "ignored"] + }) + ] + }) + val expectedResult = classes.createInstanceUsingBuilder("Foo", #{ + "a" -> #["a, b: 1", ": 2", "Another string"] + }) + + val translation = classes.createTranslation("FooBar", #["Bar"], "Foo"); + assertEquals(expectedResult, translation.invokeFunc(expectedResult.class, #[bar])) + } + + @Test + def void testTranslationWithMetadata() { + val code = ''' + metaType key string + metaType id string + metaType reference string + metaType template string + metaType location string + metaType address string + + type Foo: + [metadata key] + a int (1..1) + [metadata id] + self Foo (1..1) + [metadata reference] + value string (0..1) + [metadata scheme] + + type Bar: + [metadata key] + [metadata template] + a int (1..1) + [metadata address] + b Foo (1..1) + [metadata location] + + type Inp: + + translate source FooBar { + Foo from Inp: + [meta key from "self"] + + a + [from 42] + [meta id from "favoriteNumber"] + + self + [meta reference from "self"] + + value + [from "a"] + [meta scheme from "schemeA"] + + Bar from Inp: + [meta key from "My key"] + [meta template from "My template"] + + a + [from 42] + [meta address from "Some address"] + + b + [from item] + [meta location from "My location"] + } + '''.generateCode + + val classes = code.compileToClasses + + val inp = classes.createInstanceUsingBuilder("Inp", #{}) + val expectedFoo = classes.createInstanceUsingBuilder("Foo", #{ + "meta" -> classes.createInstanceUsingBuilder(new RootPackage("com.rosetta.model.metafields"), "MetaFields", #{ + "externalKey" -> "self" + }), + "a" -> classes.createInstanceUsingBuilder(new RootPackage("com.rosetta.model.metafields"), "FieldWithMetaInteger", #{ + "value" -> 42, + "meta" -> classes.createInstanceUsingBuilder(new RootPackage("com.rosetta.model.metafields"), "MetaFields", #{ + "externalKey" -> "favoriteNumber" + }) + }), + "self" -> classes.createInstanceUsingBuilder(new RootPackage("com.rosetta.test.model.metafields"), "ReferenceWithMetaFoo", #{ + "externalReference" -> "self" + }), + "value" -> classes.createInstanceUsingBuilder(new RootPackage("com.rosetta.model.metafields"), "FieldWithMetaString", #{ + "value" -> "a", + "meta" -> classes.createInstanceUsingBuilder(new RootPackage("com.rosetta.model.metafields"), "MetaFields", #{ + "scheme" -> "schemeA" + }) + }) + }) + val expectedBar = classes.createInstanceUsingBuilder("Bar", #{ + "meta" -> classes.createInstanceUsingBuilder(new RootPackage("com.rosetta.model.metafields"), "MetaAndTemplateFields", #{ + "externalKey" -> "My key", + "template" -> "My template" + }), + "a" -> classes.createInstanceUsingBuilder(new RootPackage("com.rosetta.model.metafields"), "ReferenceWithMetaInteger", #{ + "value" -> 42, + "reference" -> Reference.builder.setScope("DOCUMENT").setReference("Some address") + }), + "b" -> classes.createInstanceUsingBuilder(new RootPackage("com.rosetta.test.model.metafields"), "FieldWithMetaFoo", #{ + "value" -> expectedFoo, + "meta" -> classes.createInstanceUsingBuilder(new RootPackage("com.rosetta.model.metafields"), "MetaFields", #{ + "key" -> #[Key.builder.setScope("DOCUMENT").setKeyValue("My location")] + }) + }) + }) + + val translation = classes.createTranslation("FooBar", #["Inp"], "Bar"); + assertEquals(expectedBar, translation.invokeFunc(expectedBar.class, #[inp])) + } + +} \ No newline at end of file diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/translate/TranslateTestUtil.java b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/translate/TranslateTestUtil.java new file mode 100644 index 000000000..6d71b222a --- /dev/null +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/generator/java/translate/TranslateTestUtil.java @@ -0,0 +1,38 @@ +package com.regnosys.rosetta.generator.java.translate; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import com.google.inject.Injector; +import com.regnosys.rosetta.tests.util.ModelHelper; +import com.rosetta.model.lib.ModelSymbolId; +import com.rosetta.model.lib.ModelTranslationId; +import com.rosetta.model.lib.functions.RosettaFunction; +import com.rosetta.util.types.JavaClass; +import com.rosetta.util.types.generated.GeneratedJavaClassService; + +public class TranslateTestUtil { + private final Injector injector; + private final GeneratedJavaClassService generatedJavaClassService; + private final ModelHelper modelHelper; + + @Inject + public TranslateTestUtil(Injector injector, GeneratedJavaClassService generatedJavaClassService, ModelHelper modelHelper) { + this.injector = injector; + this.generatedJavaClassService = generatedJavaClassService; + this.modelHelper = modelHelper; + } + + public RosettaFunction createTranslation(Map> classes, String sourceName, List inputNames, String outputName) { + ModelTranslationId id = new ModelTranslationId( + new ModelSymbolId(modelHelper.rootPackage(), sourceName), + inputNames.stream().map(n -> new ModelSymbolId(modelHelper.rootPackage(), n)).collect(Collectors.toList()), + new ModelSymbolId(modelHelper.rootPackage(), outputName) + ); + JavaClass classRepr = generatedJavaClassService.toJavaTranslationFunction(id); + return (RosettaFunction)injector.getInstance(classes.get(classRepr.getCanonicalName().toString())); + } +} diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaParsingTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaParsingTest.xtend index 7889e151e..a78c899bf 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaParsingTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaParsingTest.xtend @@ -34,20 +34,62 @@ class RosettaParsingTest { @Inject extension ValidationTestHelper @Inject extension ExpressionParser + @Test + def void testTwoModelsSameNamespaceReferencesEachOther() { + val model1 = ''' + namespace test + + type A: + id string (1..1) + ''' + + val model2 = ''' + namespace test + + + type B: + a A (1..1) + ''' + + #[model1, model2].parseRosettaWithNoIssues + } + @Test def void testScopingForImplicitFeatureWithSameNameAsAnnotation() { val model = ''' annotation foo: - + type Bar: foo date (1..1) '''.parseRosettaWithNoIssues - + "bar extract foo -> day" .parseExpression(#[model], #["bar Bar (1..1)"]) .assertNoIssues } - + + @Test + def void testCanUseAlisesWhenImpoting() { + val model1 = ''' + namespace foo.bar + + type A: + id string (1..1) + ''' + + val model2 = ''' + namespace test + + import foo.bar.* as someAlias + + + type B: + a someAlias.A (1..1) + ''' + + #[model1, model2].parseRosettaWithNoIssues + } + @Test def void testValidDefaultSyntax() { "a default 2" diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaTranslateParsingTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaTranslateParsingTest.xtend new file mode 100644 index 000000000..e7cac2c90 --- /dev/null +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/tests/RosettaTranslateParsingTest.xtend @@ -0,0 +1,47 @@ +/* + * generated by Xtext 2.10.0 + */ +package com.regnosys.rosetta.tests + +import com.regnosys.rosetta.tests.util.ModelHelper +import org.eclipse.xtext.testing.InjectWith +import org.eclipse.xtext.testing.extensions.InjectionExtension +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.^extension.ExtendWith + +import javax.inject.Inject + +@ExtendWith(InjectionExtension) +@InjectWith(RosettaInjectorProvider) +class RosettaTranslateParsingTest { + + @Inject extension ModelHelper + + @Test + def void testTranslate() { + ''' + metaType location string + + type Foo: + a string (1..1) + [metadata location] + + type Bar: + b string (1..1) + c string (1..1) + + translate source FooBar1 { + Foo from Bar: + + a + [from b] + [meta location from c] + } + + translate source FooBar2 extends FooBar1 { + Foo from bar Bar, string: + + a + [from bar -> b + item] + } + '''.parseRosettaWithNoIssues + } +} diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/RosettaTranslateValidationTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/RosettaTranslateValidationTest.xtend new file mode 100644 index 000000000..272f61fba --- /dev/null +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/RosettaTranslateValidationTest.xtend @@ -0,0 +1,256 @@ +/* + * generated by Xtext 2.10.0 + */ +package com.regnosys.rosetta.validation + +import com.regnosys.rosetta.tests.util.ModelHelper +import org.eclipse.xtext.testing.InjectWith +import org.eclipse.xtext.testing.extensions.InjectionExtension +import org.eclipse.xtext.testing.validation.ValidationTestHelper +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.^extension.ExtendWith + +import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.* +import static com.regnosys.rosetta.rosetta.translate.TranslatePackage.Literals.* +import javax.inject.Inject + +@ExtendWith(InjectionExtension) +@InjectWith(MyRosettaInjectorProvider) +class RosettaTranslateValidationTest implements RosettaIssueCodes { + + @Inject extension ValidationTestHelper + @Inject extension ModelHelper + + @Test + def void testDuplicateParameterNamesAreDisallowed() { + val model = ''' + type Foo: + a string (1..1) + + type Bar: + b string (1..1) + + translate source FooBar { + Foo from var Bar, var string: + + a + [from var] + } + '''.parseRosetta + + model.assertError(TRANSLATION_PARAMETER, null, + "Duplicate parameter name `var`." + ) + } + + @Test + def void testMaxOneUnnamedParameter() { + val model = ''' + type Foo: + a string (1..1) + + type Bar: + b string (1..1) + + translate source FooBar { + Foo from Bar, string: + + a + [from b] + } + '''.parseRosetta + + model.assertError(TRANSLATION_PARAMETER, null, + "Cannot have multiple unnamed parameters." + ) + } + + @Test + def void testMultiToSingleCardinalityIsDisallowed() { + val model = ''' + type Foo: + a string (1..1) + + type Bar: + b string (0..2) + + translate source FooBar { + Foo from Bar: + + a + [from b] + } + '''.parseRosetta + + model.assertError(ROSETTA_SYMBOL_REFERENCE, null, + "Expression must be of single cardinality when mapping to attribute `a` of single cardinality." + ) + } + + @Test + def void testTypeTranslationMustBeSingle() { + val model = ''' + type Foo: + a string (1..1) + + type Bar: + b string (0..2) + + translate source FooBar { + Foo from Bar: + [from b] + + Foo from string: + + a + [from item] + } + '''.parseRosetta + + model.assertError(ROSETTA_EXPRESSION, null, + "Expression must be of single cardinality when mapping to a type." + ) + } + + @Test + def void testMetaTranslationMustBeSingle() { + val model = ''' + type Foo: + a string (1..1) + [metadata scheme] + + type Bar: + b string (0..2) + + translate source FooBar { + Foo from Bar: + + a + [meta scheme from b] + } + '''.parseRosetta + + model.assertError(ROSETTA_EXPRESSION, null, + "Expression must be of single cardinality when mapping to attribute `scheme` of single cardinality." + ) + } + + @Test + def void testMatchingTranslationMustExist() { + val model = ''' + type Foo: + a string (1..1) + + type Bar: + b Qux (1..1) + + type Qux: + c string (1..1) + + translate source FooBar { + Foo from Bar: + + a + [from b] + } + '''.parseRosetta + + model.assertError(TRANSLATE_INSTRUCTION, null, + "No translation exists to translate Qux into string." + ) + } + + @Test + def void testMatchingTypeTranslationMustExist() { + val model = ''' + type Foo: + a string (1..1) + + type Bar: + b Qux (1..1) + + type Qux: + c string (1..1) + + translate source FooBar { + Foo from Bar: + [from b] + } + '''.parseRosetta + + model.assertError(TRANSLATE_INSTRUCTION, null, + "No translation exists to translate Qux into Foo." + ) + } + + @Test + def void testMatchingTranslationExists() { + ''' + type Foo: + a string (1..1) + + type Bar: + b Qux (1..1) + + type Qux: + c string (1..1) + + translate source FooBar { + Foo from Bar: + + a + [from b, 42] + + string from Qux, context number: + [from c] + } + '''.parseRosettaWithNoIssues + } + + @Test + def void testMatchingTranslationExistsInParent() { + ''' + type Foo: + a string (1..1) + + type Bar: + b Qux (1..1) + + type Qux: + c string (1..1) + + translate source Parent { + string from Qux: + [from c] + } + + translate source FooBar extends Parent { + Foo from Bar: + + a + [from b] + } + '''.parseRosettaWithNoIssues + } + + @Test + def void testMatchingTranslationWithExtendsExists() { + ''' + type Foo: + a AttrType (1..1) + + type SuperAttrType: + + type AttrType extends SuperAttrType: + sub string (1..1) + + type Bar: + b Qux (1..1) + + type SuperQux: + + type Qux extends SuperQux: + c string (1..1) + + translate source FooBar { + Foo from Bar: + + a + [from b] + + AttrType from SuperQux: + } + '''.parseRosettaWithNoIssues + } +} diff --git a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/RosettaValidatorTest.xtend b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/RosettaValidatorTest.xtend index ee3912003..a07dc0c85 100644 --- a/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/RosettaValidatorTest.xtend +++ b/rosetta-testing/src/test/java/com/regnosys/rosetta/validation/RosettaValidatorTest.xtend @@ -20,6 +20,8 @@ import org.junit.jupiter.api.^extension.ExtendWith import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.* import static com.regnosys.rosetta.rosetta.simple.SimplePackage.Literals.* import static com.regnosys.rosetta.rosetta.expression.ExpressionPackage.Literals.* +import static com.regnosys.rosetta.rosetta.RosettaPackage.Literals.* + import javax.inject.Inject import com.regnosys.rosetta.tests.util.ExpressionParser @@ -31,6 +33,42 @@ class RosettaValidatorTest implements RosettaIssueCodes { @Inject extension ModelHelper @Inject extension ExpressionParser + @Test + def void testCannotUseImportAliasesWithoutWildcard() { + val model = ''' + import foo.bar.Test as someAlias + '''.parseRosetta + + model.assertError(IMPORT, null, + '"as" statement can only be used with wildcard import' + ) + } + + + //TODO: write a validation for when the user forgets the alias + @Test + def void testCanUserImportAlisesWhenWildcardPresent() { + val model1 = ''' + namespace foo.bar + + type A: + id string (1..1) + ''' + + val model2 = ''' + namespace test + + import foo.bar.* as someAlias + + + + type B: + a someAlias.A (1..1) + ''' + + #[model1, model2].parseRosettaWithNoIssues + } + @Test def void testCannotAccessUncommonMetaFeatureOfDeepFeatureCall() { val model = '''