diff --git a/.gradle/9.1.0/checksums/checksums.lock b/.gradle/9.1.0/checksums/checksums.lock deleted file mode 100644 index 8899156..0000000 Binary files a/.gradle/9.1.0/checksums/checksums.lock and /dev/null differ diff --git a/.gradle/9.1.0/checksums/md5-checksums.bin b/.gradle/9.1.0/checksums/md5-checksums.bin deleted file mode 100644 index 5c952ce..0000000 Binary files a/.gradle/9.1.0/checksums/md5-checksums.bin and /dev/null differ diff --git a/.gradle/9.1.0/checksums/sha1-checksums.bin b/.gradle/9.1.0/checksums/sha1-checksums.bin deleted file mode 100644 index e19eb88..0000000 Binary files a/.gradle/9.1.0/checksums/sha1-checksums.bin and /dev/null differ diff --git a/.gradle/9.1.0/fileChanges/last-build.bin b/.gradle/9.1.0/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/.gradle/9.1.0/fileChanges/last-build.bin and /dev/null differ diff --git a/.gradle/9.1.0/fileHashes/fileHashes.lock b/.gradle/9.1.0/fileHashes/fileHashes.lock deleted file mode 100644 index 7c4049e..0000000 Binary files a/.gradle/9.1.0/fileHashes/fileHashes.lock and /dev/null differ diff --git a/.gradle/9.1.0/gc.properties b/.gradle/9.1.0/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index c4c636a..0000000 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and /dev/null differ diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index 195f502..0000000 --- a/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Mon Sep 29 13:56:43 UTC 2025 -gradle.version=9.1.0 diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties deleted file mode 100644 index e69de29..0000000 diff --git a/src/commonMain/kotlin/org/litlfred/fmlrunner/FmlRunner.kt b/src/commonMain/kotlin/org/litlfred/fmlrunner/FmlRunner.kt index 0814f35..07c66fe 100644 --- a/src/commonMain/kotlin/org/litlfred/fmlrunner/FmlRunner.kt +++ b/src/commonMain/kotlin/org/litlfred/fmlrunner/FmlRunner.kt @@ -28,6 +28,13 @@ class FmlRunner { return compiler.compile(fmlContent) } + /** + * Validate FML syntax without full compilation + */ + fun validateFmlSyntax(fmlContent: String): FmlSyntaxValidationResult { + return compiler.validateSyntax(fmlContent) + } + /** * Execute StructureMap on input content */ diff --git a/src/commonMain/kotlin/org/litlfred/fmlrunner/compiler/FmlCompiler.kt b/src/commonMain/kotlin/org/litlfred/fmlrunner/compiler/FmlCompiler.kt index 17b7cbf..0449ee1 100644 --- a/src/commonMain/kotlin/org/litlfred/fmlrunner/compiler/FmlCompiler.kt +++ b/src/commonMain/kotlin/org/litlfred/fmlrunner/compiler/FmlCompiler.kt @@ -464,4 +464,30 @@ class FmlCompiler { ) } } + + /** + * Validate FML syntax without full compilation + */ + fun validateSyntax(fmlContent: String): FmlSyntaxValidationResult { + return try { + // Tokenize + val tokenizer = FmlTokenizer(fmlContent) + val tokens = tokenizer.tokenize() + + // Parse for syntax validation (without requiring complete StructureMap) + val parser = FmlParser(tokens) + val result = parser.parse() + + FmlSyntaxValidationResult( + valid = result.success, + errors = result.errors, + warnings = result.warnings + ) + } catch (e: Exception) { + FmlSyntaxValidationResult( + valid = false, + errors = listOf("Syntax validation failed: ${e.message}") + ) + } + } } \ No newline at end of file diff --git a/src/commonMain/kotlin/org/litlfred/fmlrunner/types/FhirTypes.kt b/src/commonMain/kotlin/org/litlfred/fmlrunner/types/FhirTypes.kt index 3975c6f..0fa4f3d 100644 --- a/src/commonMain/kotlin/org/litlfred/fmlrunner/types/FhirTypes.kt +++ b/src/commonMain/kotlin/org/litlfred/fmlrunner/types/FhirTypes.kt @@ -94,6 +94,16 @@ data class FmlCompilationResult( val warnings: List = emptyList() ) +/** + * FML Syntax Validation Result + */ +@Serializable +data class FmlSyntaxValidationResult( + val valid: Boolean, + val errors: List = emptyList(), + val warnings: List = emptyList() +) + /** * Execution Result for StructureMap execution */ diff --git a/src/commonTest/kotlin/org/litlfred/fmlrunner/BasicTest.kt b/src/commonTest/kotlin/org/litlfred/fmlrunner/BasicTest.kt index 6d8e53b..2578465 100644 --- a/src/commonTest/kotlin/org/litlfred/fmlrunner/BasicTest.kt +++ b/src/commonTest/kotlin/org/litlfred/fmlrunner/BasicTest.kt @@ -95,4 +95,98 @@ class FmlRunnerTest { assertTrue(result.success, "Execution should succeed") } + + @Test + fun testValidFmlSyntaxValidation() { + val runner = FmlRunner() + val validFmlContent = """ + map "http://example.org/StructureMap/Patient" = "PatientTransform" + + group main(source src, target tgt) { + src.name -> tgt.fullName; + src.active -> tgt.isActive; + } + """.trimIndent() + + val result = runner.validateFmlSyntax(validFmlContent) + assertTrue(result.valid, "Valid FML syntax should pass validation") + assertTrue(result.errors.isEmpty(), "No errors should be reported for valid syntax") + } + + @Test + fun testInvalidFmlSyntaxValidation_MissingMapKeyword() { + val runner = FmlRunner() + val invalidFmlContent = """ + "http://example.org/StructureMap/Patient" = "PatientTransform" + + group main(source src, target tgt) { + src.name -> tgt.fullName; + } + """.trimIndent() + + val result = runner.validateFmlSyntax(invalidFmlContent) + assertTrue(!result.valid, "Invalid FML syntax should fail validation") + assertTrue(result.errors.isNotEmpty(), "Errors should be reported for invalid syntax") + assertTrue(result.errors.any { it.contains("map") || it.contains("Expected") }, + "Error should mention missing 'map' keyword") + } + + @Test + fun testInvalidFmlSyntaxValidation_MalformedGroup() { + val runner = FmlRunner() + val invalidFmlContent = """ + map "http://example.org/StructureMap/Patient" = "PatientTransform" + + group main(source src, target tgt { + src.name -> tgt.fullName; + } + """.trimIndent() + + val result = runner.validateFmlSyntax(invalidFmlContent) + assertTrue(!result.valid, "Invalid FML syntax should fail validation") + assertTrue(result.errors.isNotEmpty(), "Errors should be reported for malformed syntax") + } + + @Test + fun testInvalidFmlSyntaxValidation_EmptyContent() { + val runner = FmlRunner() + val emptyContent = "" + + val result = runner.validateFmlSyntax(emptyContent) + assertTrue(!result.valid, "Empty content should fail validation") + assertTrue(result.errors.isNotEmpty(), "Errors should be reported for empty content") + } + + @Test + fun testInvalidFmlSyntaxValidation_MissingClosingBrace() { + val runner = FmlRunner() + val invalidFmlContent = """ + map "http://example.org/StructureMap/Patient" = "PatientTransform" + + group main(source src, target tgt) { + src.name -> tgt.fullName; + """.trimIndent() + + val result = runner.validateFmlSyntax(invalidFmlContent) + assertTrue(!result.valid, "Invalid FML syntax should fail validation") + assertTrue(result.errors.isNotEmpty(), "Errors should be reported for missing closing brace") + } + + @Test + fun testComplexValidFmlSyntaxValidation() { + val runner = FmlRunner() + val complexValidFmlContent = """ + map "http://example.org/StructureMap/Complex" = "ComplexTransform" + + group main(source src, target tgt) { + src.name -> tgt.fullName; + src.active -> tgt.isActive; + src.email -> tgt.contactEmail; + } + """.trimIndent() + + val result = runner.validateFmlSyntax(complexValidFmlContent) + assertTrue(result.valid, "Complex valid FML syntax should pass validation") + assertTrue(result.errors.isEmpty(), "No errors should be reported for complex valid syntax") + } } \ No newline at end of file