diff --git a/reactor/.gitignore b/reactor/.gitignore new file mode 100644 index 00000000..669f386f --- /dev/null +++ b/reactor/.gitignore @@ -0,0 +1,5 @@ +.idea/workspace.xml +out/** +API/target/** +Core/target/** +Test/target/** diff --git a/reactor/.idea/.name b/reactor/.idea/.name new file mode 100644 index 00000000..6e1d9535 --- /dev/null +++ b/reactor/.idea/.name @@ -0,0 +1 @@ +Reactor \ No newline at end of file diff --git a/reactor/.idea/codeStyleSettings.xml b/reactor/.idea/codeStyleSettings.xml new file mode 100644 index 00000000..03a62570 --- /dev/null +++ b/reactor/.idea/codeStyleSettings.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/reactor/.idea/codeStyles/Project.xml b/reactor/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..e4213e8d --- /dev/null +++ b/reactor/.idea/codeStyles/Project.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/codeStyles/codeStyleConfig.xml b/reactor/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/reactor/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/reactor/.idea/compiler.xml b/reactor/.idea/compiler.xml new file mode 100644 index 00000000..3639a556 --- /dev/null +++ b/reactor/.idea/compiler.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/copyright/Apache_2_0.xml b/reactor/.idea/copyright/Apache_2_0.xml new file mode 100644 index 00000000..f47c2c3d --- /dev/null +++ b/reactor/.idea/copyright/Apache_2_0.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/reactor/.idea/copyright/profiles_settings.xml b/reactor/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..87af87ca --- /dev/null +++ b/reactor/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/encodings.xml b/reactor/.idea/encodings.xml new file mode 100644 index 00000000..1db08596 --- /dev/null +++ b/reactor/.idea/encodings.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/kotlinc.xml b/reactor/.idea/kotlinc.xml new file mode 100644 index 00000000..5806fb33 --- /dev/null +++ b/reactor/.idea/kotlinc.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__com_github_andrewoma_dexx_collection_0_7.xml b/reactor/.idea/libraries/Maven__com_github_andrewoma_dexx_collection_0_7.xml new file mode 100644 index 00000000..c1cd535e --- /dev/null +++ b/reactor/.idea/libraries/Maven__com_github_andrewoma_dexx_collection_0_7.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__com_google_code_findbugs_jsr305_2_0_1.xml b/reactor/.idea/libraries/Maven__com_google_code_findbugs_jsr305_2_0_1.xml new file mode 100644 index 00000000..dd1dc9f0 --- /dev/null +++ b/reactor/.idea/libraries/Maven__com_google_code_findbugs_jsr305_2_0_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__com_intellij_annotations_12_0.xml b/reactor/.idea/libraries/Maven__com_intellij_annotations_12_0.xml new file mode 100644 index 00000000..a5eed724 --- /dev/null +++ b/reactor/.idea/libraries/Maven__com_intellij_annotations_12_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__commons_io_commons_io_2_4.xml b/reactor/.idea/libraries/Maven__commons_io_commons_io_2_4.xml new file mode 100644 index 00000000..bc2aad0e --- /dev/null +++ b/reactor/.idea/libraries/Maven__commons_io_commons_io_2_4.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__junit_junit_4_12.xml b/reactor/.idea/libraries/Maven__junit_junit_4_12.xml new file mode 100644 index 00000000..d4110417 --- /dev/null +++ b/reactor/.idea/libraries/Maven__junit_junit_4_12.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__net_sf_trove4j_trove4j_3_0_3.xml b/reactor/.idea/libraries/Maven__net_sf_trove4j_trove4j_3_0_3.xml new file mode 100644 index 00000000..72d4d7e6 --- /dev/null +++ b/reactor/.idea/libraries/Maven__net_sf_trove4j_trove4j_3_0_3.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_maven_aether_provider_3_0_5.xml b/reactor/.idea/libraries/Maven__org_apache_maven_maven_aether_provider_3_0_5.xml new file mode 100644 index 00000000..e2856a11 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_maven_aether_provider_3_0_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_maven_artifact_3_0_5.xml b/reactor/.idea/libraries/Maven__org_apache_maven_maven_artifact_3_0_5.xml new file mode 100644 index 00000000..84b1f3b0 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_maven_artifact_3_0_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_maven_core_3_0_5.xml b/reactor/.idea/libraries/Maven__org_apache_maven_maven_core_3_0_5.xml new file mode 100644 index 00000000..7b4528f4 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_maven_core_3_0_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_maven_model_3_0_5.xml b/reactor/.idea/libraries/Maven__org_apache_maven_maven_model_3_0_5.xml new file mode 100644 index 00000000..a40a8c69 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_maven_model_3_0_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_maven_model_builder_3_0_5.xml b/reactor/.idea/libraries/Maven__org_apache_maven_maven_model_builder_3_0_5.xml new file mode 100644 index 00000000..cb079349 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_maven_model_builder_3_0_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_maven_plugin_api_3_0_5.xml b/reactor/.idea/libraries/Maven__org_apache_maven_maven_plugin_api_3_0_5.xml new file mode 100644 index 00000000..3448e808 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_maven_plugin_api_3_0_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_maven_repository_metadata_3_0_5.xml b/reactor/.idea/libraries/Maven__org_apache_maven_maven_repository_metadata_3_0_5.xml new file mode 100644 index 00000000..8b3c1337 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_maven_repository_metadata_3_0_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_maven_settings_3_0_5.xml b/reactor/.idea/libraries/Maven__org_apache_maven_maven_settings_3_0_5.xml new file mode 100644 index 00000000..1bc8a7b6 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_maven_settings_3_0_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_maven_settings_builder_3_0_5.xml b/reactor/.idea/libraries/Maven__org_apache_maven_maven_settings_builder_3_0_5.xml new file mode 100644 index 00000000..4e605706 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_maven_settings_builder_3_0_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_maven_toolchain_2_2_1.xml b/reactor/.idea/libraries/Maven__org_apache_maven_maven_toolchain_2_2_1.xml new file mode 100644 index 00000000..3dfc84db --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_maven_toolchain_2_2_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_plugin_tools_maven_plugin_annotations_3_4.xml b/reactor/.idea/libraries/Maven__org_apache_maven_plugin_tools_maven_plugin_annotations_3_4.xml new file mode 100644 index 00000000..2c2167e6 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_plugin_tools_maven_plugin_annotations_3_4.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_plugins_maven_compiler_plugin_3_5_1.xml b/reactor/.idea/libraries/Maven__org_apache_maven_plugins_maven_compiler_plugin_3_5_1.xml new file mode 100644 index 00000000..beb3bd61 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_plugins_maven_compiler_plugin_3_5_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_shared_maven_shared_incremental_1_1.xml b/reactor/.idea/libraries/Maven__org_apache_maven_shared_maven_shared_incremental_1_1.xml new file mode 100644 index 00000000..86cb928e --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_shared_maven_shared_incremental_1_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_apache_maven_shared_maven_shared_utils_3_0_0.xml b/reactor/.idea/libraries/Maven__org_apache_maven_shared_maven_shared_utils_3_0_0.xml new file mode 100644 index 00000000..af5282a3 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_apache_maven_shared_maven_shared_utils_3_0_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_classworlds_2_4.xml b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_classworlds_2_4.xml new file mode 100644 index 00000000..e8e872cd --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_classworlds_2_4.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_compiler_api_2_7.xml b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_compiler_api_2_7.xml new file mode 100644 index 00000000..e31500c7 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_compiler_api_2_7.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_compiler_javac_2_7.xml b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_compiler_javac_2_7.xml new file mode 100644 index 00000000..3346b9a0 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_compiler_javac_2_7.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_compiler_manager_2_7.xml b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_compiler_manager_2_7.xml new file mode 100644 index 00000000..bf3b7b81 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_compiler_manager_2_7.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_component_annotations_1_5_5.xml b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_component_annotations_1_5_5.xml new file mode 100644 index 00000000..92923179 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_component_annotations_1_5_5.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_interpolation_1_14.xml b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_interpolation_1_14.xml new file mode 100644 index 00000000..ec4dd79b --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_interpolation_1_14.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_utils_2_0_6.xml b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_utils_2_0_6.xml new file mode 100644 index 00000000..6902ba32 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_codehaus_plexus_plexus_utils_2_0_6.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml b/reactor/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml new file mode 100644 index 00000000..f58bbc11 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_jetbrains_annotations_13_0.xml b/reactor/.idea/libraries/Maven__org_jetbrains_annotations_13_0.xml new file mode 100644 index 00000000..e2c82975 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_jetbrains_annotations_13_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_compiler_1_2_30.xml b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_compiler_1_2_30.xml new file mode 100644 index 00000000..4458da82 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_compiler_1_2_30.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_maven_plugin_1_2_30.xml b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_maven_plugin_1_2_30.xml new file mode 100644 index 00000000..66cbded6 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_maven_plugin_1_2_30.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_reflect_1_2_30.xml b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_reflect_1_2_30.xml new file mode 100644 index 00000000..f6897471 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_reflect_1_2_30.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_runtime_1_2_30.xml b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_runtime_1_2_30.xml new file mode 100644 index 00000000..e9f1b4a0 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_runtime_1_2_30.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_script_runtime_1_2_30.xml b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_script_runtime_1_2_30.xml new file mode 100644 index 00000000..e19c1891 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_script_runtime_1_2_30.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_stdlib_1_2_30.xml b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_stdlib_1_2_30.xml new file mode 100644 index 00000000..e43fa720 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_jetbrains_kotlin_kotlin_stdlib_1_2_30.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_api_1_13_1.xml b/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_api_1_13_1.xml new file mode 100644 index 00000000..1d51c72c --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_api_1_13_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_impl_1_13_1.xml b/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_impl_1_13_1.xml new file mode 100644 index 00000000..ea265e9f --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_impl_1_13_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_spi_1_13_1.xml b/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_spi_1_13_1.xml new file mode 100644 index 00000000..17d14328 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_spi_1_13_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_util_1_13_1.xml b/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_util_1_13_1.xml new file mode 100644 index 00000000..52b69b1b --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_sonatype_aether_aether_util_1_13_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_sonatype_plexus_plexus_cipher_1_4.xml b/reactor/.idea/libraries/Maven__org_sonatype_plexus_plexus_cipher_1_4.xml new file mode 100644 index 00000000..7316193d --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_sonatype_plexus_plexus_cipher_1_4.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_sonatype_plexus_plexus_sec_dispatcher_1_3.xml b/reactor/.idea/libraries/Maven__org_sonatype_plexus_plexus_sec_dispatcher_1_3.xml new file mode 100644 index 00000000..bb61d0e6 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_sonatype_plexus_plexus_sec_dispatcher_1_3.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_guava_0_9_9.xml b/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_guava_0_9_9.xml new file mode 100644 index 00000000..bea2ae89 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_guava_0_9_9.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_guice_no_aop_3_1_0.xml b/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_guice_no_aop_3_1_0.xml new file mode 100644 index 00000000..176a83f0 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_guice_no_aop_3_1_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_inject_bean_2_3_0.xml b/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_inject_bean_2_3_0.xml new file mode 100644 index 00000000..c8c20328 --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_inject_bean_2_3_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_inject_plexus_2_3_0.xml b/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_inject_plexus_2_3_0.xml new file mode 100644 index 00000000..1209103f --- /dev/null +++ b/reactor/.idea/libraries/Maven__org_sonatype_sisu_sisu_inject_plexus_2_3_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/misc.xml b/reactor/.idea/misc.xml new file mode 100644 index 00000000..ad9d2b12 --- /dev/null +++ b/reactor/.idea/misc.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/modules.xml b/reactor/.idea/modules.xml new file mode 100644 index 00000000..80bd7007 --- /dev/null +++ b/reactor/.idea/modules.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/scopes/source.xml b/reactor/.idea/scopes/source.xml new file mode 100644 index 00000000..91e8ffd3 --- /dev/null +++ b/reactor/.idea/scopes/source.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/reactor/.idea/uiDesigner.xml b/reactor/.idea/uiDesigner.xml new file mode 100644 index 00000000..e96534fb --- /dev/null +++ b/reactor/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/.idea/vcs.xml b/reactor/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/reactor/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/reactor/.travis.yml b/reactor/.travis.yml new file mode 100644 index 00000000..31e4f62b --- /dev/null +++ b/reactor/.travis.yml @@ -0,0 +1,14 @@ +language: java +jdk: +- oraclejdk8 +script: mvn -Dmaven.javadoc.skip=true -B package +deploy: + provider: releases + api_key: + secure: lDb8+36TFmBXll5ty4eSx6KIDjOCBcnHN3V/JANvHqsydkXNXoeQSFigEh4CkmOeAdbC9C49JK8StohYT3w8nx4F8N0pwtYlHSEfXVj9HZCOGxJvuO1scc7PJOgxZnNxC/EQZSy6Wqv/G6WwTUZm5tU6PH3yxxYYm4kpXTMHVAPeUeCI7VkrRdASUkGjZ74c2CDD0pQwmY+tisapXWyEdZ59/ozuRCeTEqriEDhCXbvKjp0Th3MoQnBRUy0gFa73JSYLAaPtfQULwZiRu+foP3pqiWQ46ZRs2uqtv97B/1pAeZsSNahjXpQKjuA8rRfBL91crVH73KvCizggN9Ok4pDUvydmKlUh1/Qv1QqZnxx2/lj5SdCAzAuq8NZdGBd98BcWF7TPVRfhvkaAmceobR3IqzhJe1+a1Ui5Hx5elQpClMFlnk39Behi3hK0HSnRRtf/2b8VpO6zpGE5XFa9DwruDvNcatyXnAdnFbRTbhWDuzpGPPld2QwMYT9day5KhupUdkl+otJ4wLFfRIBOqoIyUFkGfvNuGy5f1dy9ePXbiw0u8Oysat0XWQ6Qj+jLpGQqMh8hSOVvcJmpIbowptnB2DEDSDu98gs3jBu1ooFUNYWtLGtm0IpvzVMATrZa5Rug4gdXjs9H8JqZ6sss0URTvMHi/B+I/Bm0XqZ/Uic= + file: + - ./API/target/conreactor-api-0.9.jar + - ./Core/target/conreactor-core-0.9.jar + skip_cleanup: true + on: + tags: true diff --git a/reactor/API/API.iml b/reactor/API/API.iml new file mode 100644 index 00000000..b5309b5b --- /dev/null +++ b/reactor/API/API.iml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/API/pom.xml b/reactor/API/pom.xml new file mode 100644 index 00000000..18807e50 --- /dev/null +++ b/reactor/API/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + + jetbrains.mps.logic.conreactor + conreactor-main + 0.9 + .. + + + conreactor-api + jar + + + ${basedir}/src/ + + + \ No newline at end of file diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/AbstractSolver.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/AbstractSolver.java new file mode 100644 index 00000000..cb89b852 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/AbstractSolver.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.evaluation; + +/** + * Not very useful now. + * Serves as an abstract superclass for implementations, as a mixin of Instructible and Queryable. + * + * @author Fedor Isakov + */ +public abstract class AbstractSolver implements Instructible, Queryable { + + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/ConstraintOccurrence.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/ConstraintOccurrence.java new file mode 100644 index 00000000..cf96f48b --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/ConstraintOccurrence.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.evaluation; + + +import jetbrains.mps.logic.reactor.logical.LogicalContext; +import jetbrains.mps.logic.reactor.program.Constraint; + +import java.util.List; + +/** + * Represents a run-time object corresponding to an actual constraint. + * + * @author Fedor Isakov + */ +public interface ConstraintOccurrence { + + Constraint constraint(); + + List arguments(); + + LogicalContext logicalContext(); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFailureException.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFailureException.java new file mode 100644 index 00000000..0743133e --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationFailureException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.evaluation; + + +/** + * Thrown in case evaluation fails. + * + * @author Fedor Isakov + */ +public class EvaluationFailureException extends RuntimeException { + + public EvaluationFailureException(String message) { + super(message); + } + + public EvaluationFailureException(Throwable cause) { + super(cause); + } + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java new file mode 100644 index 00000000..35b5345c --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationSession.java @@ -0,0 +1,97 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.evaluation; + + +import jetbrains.mps.logic.reactor.logical.LogicalContext; +import jetbrains.mps.logic.reactor.program.Constraint; +import jetbrains.mps.logic.reactor.program.Predicate; +import jetbrains.mps.logic.reactor.program.PredicateSymbol; +import jetbrains.mps.logic.reactor.program.Program; + +/** + * The starting point to evaluate a program. + *

+ * The session is started with {@code newSession}, provided the backend has been initialized. + *

+ * The backend is supposed to be provided at startup. + * + * @author Fedor Isakov + */ +public abstract class EvaluationSession { + + private static EvaluationSession.Backend ourBackend; + + public static EvaluationSession current() { + if (ourBackend == null) { + throw new IllegalStateException("no backend"); + } + return ourBackend.current(); + } + + public static EvaluationSession.Config newSession(Program program) { + if (ourBackend == null) { + throw new IllegalStateException("no backend"); + } + return ourBackend.createConfig(program); + } + + protected static void setBackend(EvaluationSession.Backend backend) { + if (ourBackend != null) { + throw new IllegalStateException("backend already assigned"); + } + ourBackend = backend; + } + + protected static void clearBackend(EvaluationSession.Backend backend) { + if (ourBackend != backend) { + throw new IllegalStateException("illegal access"); + } + ourBackend = null; + } + + public abstract SessionSolver sessionSolver(); + + public abstract StoreView storeView(); + + public abstract PredicateInvocation invocation(Predicate predicate, LogicalContext logicalContext); + + public abstract ConstraintOccurrence occurrence(Constraint constraint, LogicalContext logicalContext); + + protected interface Backend { + + EvaluationSession current(); + + EvaluationSession.Config createConfig(Program program); + + } + + public static abstract class Config { + + public abstract EvaluationSession.Config withPredicates(PredicateSymbol... predicateSymbols); + + public abstract EvaluationSession.Config withTrace(EvaluationTrace computingTracer); + + public abstract EvaluationSession.Config withStoreView(StoreView storeView); + + public abstract EvaluationSession.Config withParam(String key, Object param); + + public abstract EvaluationSession start(SessionSolver sessionSolver); + + } + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationTrace.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationTrace.java new file mode 100644 index 00000000..218381ca --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/EvaluationTrace.java @@ -0,0 +1,96 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.evaluation; + + +/** + * An interface to be implemented by clients wishing to be notified of the events during evaluation. + * + * @author Fedor Isakov + */ +public interface EvaluationTrace { + + EvaluationTrace NULL = new EvaluationTrace() { + + public void activate(ConstraintOccurrence occurrence) { + } + + public void reactivate(ConstraintOccurrence occurrence) { + } + + public void suspend(ConstraintOccurrence occurrence) { + } + + public void discard(ConstraintOccurrence occurrence) { + } + + public void trying(MatchRule matchRule) { + } + + public void reject(MatchRule matchRule) { + } + + public void trigger(MatchRule matchRule) { + } + + public void retry(MatchRule matchRule) { + } + + public void finish(MatchRule matchRule) { + } + + public void tell(PredicateInvocation invocation) { + } + + public void ask(boolean result, PredicateInvocation invocation) { + } + + public void failure(EvaluationFailureException fail) { + } + + public void reportFailure(String message) { + } + + }; + + void activate(ConstraintOccurrence occurrence); + + void reactivate(ConstraintOccurrence occurrence); + + void suspend(ConstraintOccurrence occurrence); + + void discard(ConstraintOccurrence occurrence); + + void trying(MatchRule matchRule); + + void reject(MatchRule matchRule); + + void trigger(MatchRule matchRule); + + void retry(MatchRule matchRule); + + void finish(MatchRule matchRule); + + void tell(PredicateInvocation invocation); + + void ask(boolean result, PredicateInvocation invocation); + + void failure(EvaluationFailureException fail); + + @Deprecated + void reportFailure(String message); +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Instructible.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Instructible.java new file mode 100644 index 00000000..d2d4ad66 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Instructible.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.evaluation; + + +/** + * Abstract interface to a predicate. + * + * @author Fedor Isakov + */ +public interface Instructible { + + void tell(PredicateInvocation invocation); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/MatchRule.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/MatchRule.java new file mode 100644 index 00000000..deacffb1 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/MatchRule.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.evaluation; + + +import jetbrains.mps.logic.reactor.program.Rule; + +/** + * A binding of a rule and the constraints that matched its head. + * + * @author Fedor Isakov + */ +public interface MatchRule { + + Rule rule(); + + Iterable matchHeadKept(); + + Iterable matchHeadReplaced(); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/PredicateInvocation.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/PredicateInvocation.java new file mode 100644 index 00000000..1b0def4b --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/PredicateInvocation.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.evaluation; + + +import jetbrains.mps.logic.reactor.logical.LogicalContext; +import jetbrains.mps.logic.reactor.program.Predicate; + +import java.util.List; + +/** + * A run-time object representing invocation of a predicate. + * + * @author Fedor Isakov + */ +public interface PredicateInvocation { + + Predicate predicate(); + + List arguments(); + + LogicalContext logicalContext(); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Queryable.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Queryable.java new file mode 100644 index 00000000..85482933 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/Queryable.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.evaluation; + + +/** + * Abstract interface to a predicate. + * + * @author Fedor Isakov + */ +public interface Queryable extends Instructible { + + boolean ask(PredicateInvocation invocation); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java new file mode 100644 index 00000000..ba0ed3d7 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/SessionSolver.java @@ -0,0 +1,95 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.evaluation; + + +import jetbrains.mps.logic.reactor.logical.LogicalContext; +import jetbrains.mps.logic.reactor.program.AndItem; +import jetbrains.mps.logic.reactor.program.Predicate; +import jetbrains.mps.logic.reactor.program.PredicateSymbol; + +import java.util.HashMap; +import java.util.Map; + +/** + * Is used to provide an interface for handlers and solvers working together in a single session. + * + * @author Fedor Isakov + */ +public abstract class SessionSolver implements Queryable, Instructible { + + private Map solvers = new HashMap(); + private EvaluationTrace tracer = EvaluationTrace.NULL; + + public void init(PredicateSymbol... predicateSymbols) { + registerSymbols(predicateSymbols); + } + + public void init(EvaluationTrace evaluationTrace, PredicateSymbol... predicateSymbols) { + tracer = evaluationTrace; + init(predicateSymbols); + } + + @Override + public boolean ask(PredicateInvocation invocation) { + AbstractSolver solver = solver(invocation.predicate().symbol()); + boolean result = solver.ask(invocation); + tracer.ask(result, invocation); + return result; + } + + @Override + public void tell(PredicateInvocation invocation) { + AbstractSolver handler = solver(invocation.predicate().symbol()); + tracer.tell(invocation); + handler.tell(invocation); + } + + public boolean ask(Predicate predicate, LogicalContext logicalContext) { + return ask(EvaluationSession.current().invocation(predicate, logicalContext)); + } + + public void tell(AndItem item, LogicalContext logicalContext) { + if (item instanceof Predicate) { + tell(EvaluationSession.current().invocation((Predicate) item, logicalContext)); + + } else { + // FIXME: implement me + throw new UnsupportedOperationException("not implemented"); + } + } + + protected abstract void registerSymbol(PredicateSymbol predicateSymbol, EvaluationTrace computingTracer); + + protected void registerSolver(PredicateSymbol constraint, AbstractSolver solver) { + solvers.put(constraint, solver); + } + + private AbstractSolver solver(PredicateSymbol predicateSymbol) { + if (!(solvers.containsKey(predicateSymbol))) { + throw new IllegalStateException("no handler: " + predicateSymbol); + } + return solvers.get(predicateSymbol); + } + + private void registerSymbols(PredicateSymbol... predicateSymbols) { + for (PredicateSymbol symbol : predicateSymbols) { + registerSymbol(symbol, tracer); + } + } + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/StoreView.java b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/StoreView.java new file mode 100644 index 00000000..701758f8 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/evaluation/StoreView.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.evaluation; + +import jetbrains.mps.logic.reactor.program.ConstraintSymbol; + +/** + * A read-only view on the constraints store. + * + * @author Fedor Isakov + */ +public interface StoreView { + + Iterable constraintSymbols(); + + Iterable allOccurrences(); + + Iterable occurrences(ConstraintSymbol symbol); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/JoinableLogical.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/JoinableLogical.java new file mode 100644 index 00000000..3db39328 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/JoinableLogical.java @@ -0,0 +1,58 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.logical; + + +/** + * A logical variable that can be joined with another variable to produce a union. + * + * @param the value type + * + * @author Fedor Isakov + */ +public interface JoinableLogical extends Logical { + + /** + * Covariant override. + */ + JoinableLogical findRoot(); + + /** + * Unions two equivalence classes of logicals. + * Both the receiver and the {@code other} parameter are expected to be representatives. + * The one with the highest rank becomes the representative for the new class. + */ + void union(JoinableLogical other, JoinableLogical.ValueReconciler reconciler); + + /** + * Calls {@link JoinableLogical#union(JoinableLogical , JoinableLogical.ValueReconciler) } with the default value reconciler. + * The default reconciler throws {@link java.lang.IllegalArgumentException } if the two values are not equal. + */ + void union(JoinableLogical other); + + /** + * Should only be called on a representative. + */ + void setValue(T newValue); + + interface ValueReconciler { + + void reconcile(T valueA, T valueB); + + } + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/Logical.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/Logical.java new file mode 100644 index 00000000..524910a5 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/Logical.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.logical; + + +/** + * A run-time representation of a logical variable. + * + * @param the value type + * + * @author Fedor Isakov + */ +public interface Logical { + + String name(); + + /** + * Returns the representative logical instance. + *

+ * TODO: rename to 'find()' + */ + Logical findRoot(); + + /** + * Returns the value associated with this logical instance. + * Can be null. + * Only the logical instance that is a representative can have value that is not null. + *

+ * FIXME: must return the representative's value + */ + T value(); + + /** + * True iff the representative has a non-null value. + */ + boolean isBound(); + + boolean isWildcard(); + + MetaLogical metaLogical(); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalContext.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalContext.java new file mode 100644 index 00000000..a6dbb094 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalContext.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.logical; + + +/** + * Substitution of logicals for meta-logicals. + * + * @author Fedor Isakov + */ +public interface LogicalContext { + + + Logical variable(MetaLogical metaLogical); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalOwner.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalOwner.java new file mode 100644 index 00000000..793b0cdc --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/LogicalOwner.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.logical; + + +/** + * An abstract wrapper for a {@link Logical}. + * + * @author Fedor Isakov + */ +public interface LogicalOwner { + + Logical logical(); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/MetaLogical.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/MetaLogical.java new file mode 100644 index 00000000..899087b8 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/MetaLogical.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.logical; + + +/** + * A declaration of a logical variable. + * + * @param the value type + * + * @author Fedor Isakov + */ +public class MetaLogical { + + private static final String WILDCARD = "_"; + private String name; + private Class type; + private boolean wildcard = false; + + public MetaLogical(String name, Class type) { + this.name = name; + this.type = type; + } + + public MetaLogical(Class type) { + this.name = MetaLogical.WILDCARD + System.identityHashCode(this); + this.type = type; + this.wildcard = true; + } + + public String name() { + return name; + } + + public boolean isWildcard() { + return wildcard; + } + + public Class type() { + return type; + } + + @Override + public String toString() { + return name; + } + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/logical/MultiMetaLogical.java b/reactor/API/src/jetbrains/mps/logic/reactor/logical/MultiMetaLogical.java new file mode 100644 index 00000000..0bb6f919 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/logical/MultiMetaLogical.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.logical; + + +import java.util.ArrayList; +import java.util.List; + +/** + * A declaration of a logical variable with index. + * + * @param the value type + * + * @author Fedor Isakov + */ +public class MultiMetaLogical extends MetaLogical { + + private int cardinality; + private List> metaLogicals = new ArrayList>(); + + public MultiMetaLogical(String name, Class type, int cardinality) { + super(name, type); + this.cardinality = cardinality; + init(); + } + + public int cardinality() { + return cardinality; + } + + public MetaLogical logicalAt(int idx) { + return metaLogicals.get(idx); + } + + public MetaLogical[] toArray() { + MetaLogical[] array = new MetaLogical[cardinality]; + return metaLogicals.toArray(array); + } + + private void init() { + for (int i = 0; i < cardinality; i++) { + metaLogicals.add(new MetaLogical(name() + "_" + (i + 1), type())); + } + } +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/AndItem.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/AndItem.java new file mode 100644 index 00000000..f29483fc --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/AndItem.java @@ -0,0 +1,33 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + + +import java.util.List; + +/** + * An abstract conjunct. + * + * @author Fedor Isakov + */ +public interface AndItem { + + Symbol symbol(); + + List arguments(); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Constraint.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Constraint.java new file mode 100644 index 00000000..53baa70e --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Constraint.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + + +import java.util.Collection; +import java.util.List; + +/** + * A constraint provided by a handler. + * + * @author Fedor Isakov + */ +public interface Constraint extends AndItem { + + ConstraintSymbol symbol(); + + List> argumentTypes(); + + /** + * Returns the collection of predicates that need to be applied after a successful match of this collection by a + * rule's head. + * This method only returns meaningful results for constraints that serve as patterns in a rule's head. + */ + Collection patternPredicates(Collection occurrenceArgs); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/ConstraintSymbol.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/ConstraintSymbol.java new file mode 100644 index 00000000..c4f84849 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/ConstraintSymbol.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + + +/** + * A constraint symbol. + * + * @author Fedor Isakov + */ +public class ConstraintSymbol extends Symbol { + + public ConstraintSymbol(String id, int arity) { + super(id, arity); + } + + public static ConstraintSymbol symbol(String id, int arity) { + return new ConstraintSymbol(id, arity); + } + + @Override + public String toString() { + return id() + "/" + arity(); + } + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java new file mode 100644 index 00000000..7dfffa25 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Handler.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + +/** + * A handler is a container of rules. + *

+ * If the {@code primarySymbols} is a non-empty collection, only constraints with these symbols are processed + * by this handler. + * + * @author Fedor Isakov + */ +public abstract class Handler { + + public abstract String name(); + + public abstract Iterable primarySymbols(); + + public abstract Iterable rules(); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidConstraintException.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidConstraintException.java new file mode 100644 index 00000000..cd5a78b4 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidConstraintException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + + + +public class InvalidConstraintException extends Exception { + + public InvalidConstraintException(String message) { + super(message); + } + + public InvalidConstraintException(String message, Throwable ex) { + super(message, ex); + } + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidRuleException.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidRuleException.java new file mode 100644 index 00000000..06eb9870 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/InvalidRuleException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + + +public class InvalidRuleException extends Exception { + + public InvalidRuleException(String message) { + super(message); + } + + public InvalidRuleException(String message, Throwable throwable) { + super(message, throwable); + } + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/JavaPredicateSymbol.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/JavaPredicateSymbol.java new file mode 100644 index 00000000..8fcb3e8d --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/JavaPredicateSymbol.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + + +/** + * A predicate symbol for an anonymous expression. + * + * @author Fedor Isakov + */ +public class JavaPredicateSymbol extends PredicateSymbol { + + public static final JavaPredicateSymbol EXPRESSION0 = new JavaPredicateSymbol(1); + + public static final JavaPredicateSymbol EXPRESSION1 = new JavaPredicateSymbol(2); + + public static final JavaPredicateSymbol EXPRESSION2 = new JavaPredicateSymbol(3); + + public static final JavaPredicateSymbol EXPRESSION3 = new JavaPredicateSymbol(4); + + private static final String EXPRESSION = "expression"; + + private static JavaPredicateSymbol[] KNOWN_SYMBOLS = {EXPRESSION0, EXPRESSION1, EXPRESSION2, EXPRESSION3}; + + private JavaPredicateSymbol(int arity) { + super(EXPRESSION, arity); + } + + public static JavaPredicateSymbol withArity(int arity) { + return KNOWN_SYMBOLS[arity]; + } + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Predicate.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Predicate.java new file mode 100644 index 00000000..0a665538 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Predicate.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + + +/** + * A predicate that is provided by a solver. Can be either told or asked. + * + * @author Fedor Isakov + */ +public interface Predicate extends AndItem { + + PredicateSymbol symbol(); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/PredicateSymbol.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/PredicateSymbol.java new file mode 100644 index 00000000..1bf26b9c --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/PredicateSymbol.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + + +/** + * A predicate symbol. + * + * @author Fedor Isakov + */ +public class PredicateSymbol extends Symbol { + + public PredicateSymbol(String id, int arity) { + super(id, arity); + } + + @Override + public String toString() { + return id() + "()/" + arity(); + } + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Program.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Program.java new file mode 100644 index 00000000..61c4dd88 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Program.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + + +import jetbrains.mps.logic.reactor.logical.LogicalContext; + +import java.util.List; + +/** + * A collection of handlers that constitute a constraint rules program. + * + * @author Fedor Isakov + */ +public abstract class Program { + + public abstract String name(); + + public abstract Iterable constraintSymbols(); + + public abstract List> constraintArgumentTypes(ConstraintSymbol constraintSymbol); + + public abstract Iterable predicateSymbols(); + + public abstract Iterable handlers(); + + public abstract List instantiateArguments(List arguments, LogicalContext logicalContext); + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Rule.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Rule.java new file mode 100644 index 00000000..e00405ae --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Rule.java @@ -0,0 +1,50 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + + +/** + * A constraint rule description. + * + * @author Fedor Isakov + */ +public abstract class Rule { + + public abstract Rule.Kind kind(); + + public abstract String tag(); + + public abstract Iterable headKept(); + + public abstract Iterable headReplaced(); + + public abstract Iterable guard(); + + @Deprecated + public abstract Iterable body(); + + public abstract Iterable> bodyAlternation(); + + public abstract Iterable all(); + + public enum Kind { + SIMPLIFICATION(), + PROPAGATION(), + SIMPAGATION() + } + +} diff --git a/reactor/API/src/jetbrains/mps/logic/reactor/program/Symbol.java b/reactor/API/src/jetbrains/mps/logic/reactor/program/Symbol.java new file mode 100644 index 00000000..ed9f1a24 --- /dev/null +++ b/reactor/API/src/jetbrains/mps/logic/reactor/program/Symbol.java @@ -0,0 +1,70 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.program; + + +/** + * A symbol used by both constraints and predicates. Is uniquely identified by id and arity. + * + * @author Fedor Isakov + */ +public abstract class Symbol { + + private String id; + private int arity; + + protected Symbol(String id, int arity) { + this.id = id; + this.arity = arity; + } + + public String id() { + return id; + } + + public int arity() { + return arity; + } + + @Override + public int hashCode() { + int result = 17; + result = 43 * result + ((id != null ? String.valueOf(id).hashCode() : 0)); + result = 31 * result + 37 * arity; + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || this.getClass() != o.getClass()) { + return false; + } + + Symbol that = (Symbol) o; + if ((id != null ? !(id.equals(that.id())) : that.id != null)) { + return false; + } + if (arity != that.arity) { + return false; + } + + return true; + } +} diff --git a/reactor/Core/Core.iml b/reactor/Core/Core.iml new file mode 100644 index 00000000..27448936 --- /dev/null +++ b/reactor/Core/Core.iml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/Core/pom.xml b/reactor/Core/pom.xml new file mode 100644 index 00000000..281db4a7 --- /dev/null +++ b/reactor/Core/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + jetbrains.mps.logic.conreactor + conreactor-main + 0.9 + .. + + + conreactor-core + jar + + + + jetbrains.mps.logic.conreactor + conreactor-api + ${project.parent.version} + + + + + ${basedir}/src/ + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + ${basedir}/src + + + + + + + + + diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt new file mode 100644 index 00000000..5c2b7a99 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt @@ -0,0 +1,197 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import jetbrains.mps.logic.reactor.evaluation.* +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.program.Predicate +import jetbrains.mps.logic.reactor.program.Program +import jetbrains.mps.logic.reactor.program.Rule +import jetbrains.mps.logic.reactor.util.Profiler +import jetbrains.mps.logic.reactor.util.profile +import com.github.andrewoma.dexx.collection.Map as PersMap + + +class Controller( + val program: Program, + val trace: EvaluationTrace = EvaluationTrace.NULL, + val profiler: Profiler? = null, + val storeView: StoreView? = null) +{ + private val session: EvaluationSession = EvaluationSession.current() + + private val ruleIndex: RuleIndex = RuleIndex(program.handlers()) + + private val frameStack = FrameStack(storeView) + + private val matcher = Matcher(ruleIndex, profiler) + + // persistent (functional) object. reassigned on update + private var propHistory = PropagationHistory() + + internal fun currentFrame(): Frame = frameStack.current + + fun storeView(): StoreView = frameStack.current.store.view() + + fun activate(constraint: Constraint) { + try { + process(session.occurrence(constraint, noLogicalContext)) // FIXME noLogicalContext + } + catch (t: Throwable) { + throw t + } + } + + fun reactivate(occurrence: ConstraintOccurrence) { + try { + process(occurrence) + } + catch (t: Throwable) { + throw t + } + } + + /** For tests only */ + fun evaluate(occurrence: ConstraintOccurrence): StoreView { + process(occurrence) + return storeView() + } + + private fun process(active: ConstraintOccurrence) { + assert(active.isAlive()) + + profiler.profile("process_${active.constraint().symbol()}") { + + if (!active.isStored()) { + frameStack.current.store.store(active) + trace.activate(active) + } else { + trace.reactivate(active) + } + + for (match in matcher.matches(active, frameStack.current.store)) { + // TODO: paranoid check. should be isAlive() instead + if (!active.isStored()) break + if (!match.successful) continue + // FIXME: move this check elsewhere + if ((match.keptOccurrences + match.discardedOccurrences).any { co -> !co.isStored() }) continue + if (propHistory.isRecorded(match)) continue + + trace.trying(match) + + for (prd in match.patternPredicates) { + tellPredicate(prd, match.logicalContext, trace) + } + + if (!match.rule.checkGuard(match.logicalContext, trace)) { + trace.reject(match) + continue + } + + trace.trigger(match) + + for (occ in match.discardedOccurrences) { + frameStack.current.store.discard(occ) + trace.discard(occ) + } + + var failure: EvaluationFailureException? = null + + for (body in match.rule.bodyAlternation()) { + if (failure != null) { + trace.failure(failure) + trace.retry(match) + failure = null + } + + // propHistory is now functional (persistent) + // we must reassign the field on every rule triggering + // and store on the stack the last value before rule activation in order to undo in case of failure + val savedPropHistory = propHistory + this.propHistory = propHistory.record(match) + + val savedFrame = frameStack.current + frameStack.push() + + try { + for (item in body) { + when (item) { + is Constraint -> process(session.occurrence(item, match.logicalContext)) + is Predicate -> tellPredicate(item, match.logicalContext, trace) + else -> throw IllegalArgumentException("unknown item ${item}") + } + } + } + catch (ex: EvaluationFailureException) { + failure = ex + + // abrupt termination: restore the state + this.propHistory = savedPropHistory + + frameStack.reset(savedFrame) + } + + if (failure == null) { + // normal termination: skip the other alternatives + break + } + } + + if (failure != null) { + throw failure + } + + trace.finish(match) + } + + // TODO: should be isAlive() + if (active.isStored()) { + trace.suspend(active) + } + + } + } + + private fun Rule.checkGuard(logicalContext: LogicalContext, trace: EvaluationTrace): Boolean = + profiler.profile("checkGuard") { + + return guard().all { prd -> askPredicate(prd, logicalContext, trace) } + + } + + private fun askPredicate(predicate: Predicate, logicalContext: LogicalContext, trace: EvaluationTrace): Boolean = + profiler.profile("ask_${predicate.symbol()}", { + + session.sessionSolver().ask(session.invocation(predicate, logicalContext)) + + }) + + private fun tellPredicate(predicate: Predicate, logicalContext: LogicalContext, trace: EvaluationTrace) = + profiler.profile("tell_${predicate.symbol()}") { + + session.sessionSolver().tell(session.invocation(predicate, logicalContext)) + + } + +} + +private val noLogicalContext: LogicalContext = object: LogicalContext { + override fun variable(metaLogical: MetaLogical): Logical = TODO() +} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt new file mode 100644 index 00000000..098e1c0c --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/EvaluationSessionImpl.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import jetbrains.mps.logic.reactor.evaluation.* +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.program.Predicate +import jetbrains.mps.logic.reactor.program.PredicateSymbol +import jetbrains.mps.logic.reactor.program.Program +import jetbrains.mps.logic.reactor.util.Profiler +import java.util.* +import com.github.andrewoma.dexx.collection.LinkedList as PLinkedList +import com.github.andrewoma.dexx.collection.List as PList + +/** + * @author Fedor Isakov + */ + +interface SessionObjects { + + fun handler(): Controller + +} + +class EvaluationSessionImpl private constructor ( + val program: Program, + val sessionSolver: SessionSolver, + val trace: EvaluationTrace) : EvaluationSession(), SessionObjects +{ + + lateinit var controller: Controller + + private fun launch(main: Constraint, profiler: Profiler?, storeView: StoreView?) { + this.controller = Controller(program, trace, profiler, storeView) + controller.activate(main) + } + + private class Config(val program: Program) : EvaluationSession.Config() { + + val predicateSymbols = ArrayList() + val parameters = HashMap() + var evaluationTrace: EvaluationTrace = EvaluationTrace.NULL + var storeView: StoreView? = null + + override fun withPredicates(vararg predicateSymbols: PredicateSymbol): EvaluationSession.Config { + this.predicateSymbols.addAll(Arrays.asList(* predicateSymbols)) + return this + } + + override fun withTrace(computingTracer: EvaluationTrace): EvaluationSession.Config { + this.evaluationTrace = computingTracer + return this + } + + override fun withStoreView(storeView: StoreView): EvaluationSession.Config { + this.storeView = storeView + return this + } + + override fun withParam(key: String, param: Any): EvaluationSession.Config { + this.parameters.put(key, param) + return this + } + + override fun start(sessionSolver: SessionSolver): EvaluationSession { + var session = ourBackend.ourSession.get() + if (session != null) throw IllegalStateException("session already active") + + sessionSolver.init(evaluationTrace, * predicateSymbols.toArray(arrayOfNulls(predicateSymbols.size))) + + @Suppress("UNCHECKED_CAST") + val durations = + parameters.get("profiling.data") as MutableMap? + val profiler = durations?.let { Profiler() } + + session = EvaluationSessionImpl(program, sessionSolver, evaluationTrace) + ourBackend.ourSession.set(session) + try { + session.launch(parameters["main"] as Constraint, profiler, storeView) + } + finally { + try { + profiler?.run { + formattedData().entries.forEach { e -> durations?.put(e.key, e.value) } + clear() + } + } + catch (t: Throwable) { + // avoid nested failure + } + ourBackend.ourSession.set(null) + } + + return session + } + } + + override fun handler() = controller + + override fun sessionSolver(): SessionSolver = sessionSolver + + override fun storeView(): StoreView = + controller.storeView() + + override fun invocation(predicate: Predicate, logicalContext: LogicalContext): PredicateInvocation = + predicate.invocation(program.instantiateArguments(predicate.arguments(), logicalContext), logicalContext) + + override fun occurrence(constraint: Constraint, logicalContext: LogicalContext): ConstraintOccurrence = + constraint.occurrence(controller, program.instantiateArguments(constraint.arguments(), logicalContext), logicalContext) + + private class Backend : EvaluationSession.Backend { + + val ourSession = ThreadLocal() + + override fun current(): EvaluationSession = ourSession.get() ?: throw IllegalStateException("no session") + + override fun createConfig(program: Program): EvaluationSession.Config = Config(program) + } + + companion object { + private val ourBackend = Backend() + + fun init() { + setBackend(ourBackend) + } + + fun deinit() { + clearBackend(ourBackend) + } + } +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Frame.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Frame.kt new file mode 100644 index 00000000..28d71442 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Frame.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import com.github.andrewoma.dexx.collection.ConsList +import com.github.andrewoma.dexx.collection.Map +import com.github.andrewoma.dexx.collection.Maps +import jetbrains.mps.logic.reactor.evaluation.StoreView +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.util.IdWrapper +import jetbrains.mps.logic.reactor.util.cons +import jetbrains.mps.logic.reactor.util.remove +import java.util.* + +internal class Frame: LogicalObserver, StoreKeeper { + + val stack: FrameStack + + val store: Store + + private var observers: Map>, ConsList<(StoreKeeper) -> LogicalObserver>> + + constructor(stack: FrameStack) { + this.stack = stack + this.store = Store() { stack.current } + this.observers = Maps.of() + } + + constructor(stack: FrameStack, prev: Frame) { + this.stack = stack + this.store = Store(prev.store) { stack.current } + this.observers = prev.observers + } + + constructor(stack: FrameStack, storeView: StoreView) { + this.stack = stack + this.store = Store(storeView) { stack.current } + this.observers = Maps.of() + } + + override fun store() = store + + override fun addObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) { + val logicalId = IdWrapper(logical) + if (!observers.containsKey(logicalId)) { + stack.addObserver(logical) + } + this.observers = observers.put(logicalId, + observers[logicalId]?.prepend(obs) ?: cons(obs)) + } + + override fun removeObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) { + val logicalId = IdWrapper(logical) + observers[logicalId].remove(obs)?.let { newList -> + this.observers = observers.put(logicalId, newList) + if (newList.isEmpty) { + stack.removeObserver(logical) + } + } + } + + override fun valueUpdated(logical: Logical<*>) { + observers[IdWrapper(logical)]?.let { list -> + for (obs in list) { + obs(this).valueUpdated(logical) + } + } + } + + override fun parentUpdated(logical: Logical<*>) { + observers[IdWrapper(logical)]?.let { list -> + for (obs in list) { + obs(this).parentUpdated(logical) + } + } + } + +} + +internal class FrameStack(storeView: StoreView?) : LogicalObserver { + + var current: Frame + + val observing = HashSet>>() + + init { + this.current = if (storeView != null) Frame(this, storeView) else Frame(this) + } + + fun push(): Frame { + val frame = Frame(this, current) + this.current = frame + return frame + } + + fun reset(frame: Frame): Unit { + this.current = frame + } + + fun addObserver(logical: Logical<*>) { + val token = IdWrapper(logical) + if (!observing.contains(token)) { + logical.addObserver(this) + observing.add(token) + } + } + + fun removeObserver(logical: Logical<*>) { + // NOP + // yes, keep listening, the updates are still filtered down the stream + } + + override fun valueUpdated(logical: Logical<*>) { + current.valueUpdated(logical) + } + + override fun parentUpdated(logical: Logical<*>) { + current.parentUpdated(logical) + } + +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Invocation.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Invocation.kt new file mode 100644 index 00000000..ca11984a --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Invocation.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.program.Predicate + +/** + * @author Fedor Isakov + */ + +fun Predicate.invocation(arguments: List<*>, logicalContext: LogicalContext): PredicateInvocation = + Invocation(this, arguments, logicalContext) + +private data class Invocation(val predicate: Predicate, + val invocationArguments: List<*>, + val logicalContext: LogicalContext) : PredicateInvocation { + + override fun predicate(): Predicate = predicate + + override fun arguments(): List<*> = invocationArguments + + override fun logicalContext(): LogicalContext = logicalContext +} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt new file mode 100644 index 00000000..97f49e44 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Logical.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.logical.JoinableLogical +import java.util.* + +/** + * @author Fedor Isakov + */ + +interface LogicalObserver { + + fun valueUpdated(logical: Logical<*>) + + fun parentUpdated(logical: Logical<*>) + +} + +fun Logical<*>.addObserver(observer: LogicalObserver) { + (this as LogicalImpl<*>).valueObservers.add(this.to(observer)) + (this as LogicalImpl<*>).parentObservers.add(this.to(observer)) +} + +fun Logical<*>.removeObserver(observer: LogicalObserver) { + (this as LogicalImpl<*>).valueObservers.removeAll { p -> p.second == observer } + (this as LogicalImpl<*>).parentObservers.removeAll { p -> p.second == observer } +} + +fun MetaLogical.logical(): Logical = LogicalImpl(this) + +fun MetaLogical.logical(value: V): Logical = LogicalImpl(name(), value) + +class LogicalImpl : JoinableLogical { + + companion object { + var lastIdx = 0 + } + + val name: String + + val metaLogical: MetaLogical + + var _parent: LogicalImpl? = null + + var _value: T? = null + + var rank = 0 + + internal val valueObservers = ArrayList, LogicalObserver>>() + + internal val parentObservers = ArrayList, LogicalObserver>>() + + constructor(value: T) { + this.name = "$${++lastIdx}" + this.metaLogical = DefaultMetaLogical(name) + this._value = value + } + + constructor(name: String) { + this.name = "${name}_${++lastIdx}" + this.metaLogical = DefaultMetaLogical(name) + } + + constructor(name: String, value: T) { + this.name = "${name}_${++lastIdx}" + this.metaLogical = DefaultMetaLogical(name) + this._value = value + } + + constructor(metaLogical: MetaLogical) { + this.metaLogical = metaLogical + this.name = "${metaLogical.name()}_${++lastIdx}" + } + + override fun name(): String = name + + override fun value(): T? = _value + + override fun isBound(): Boolean = find()._value != null + + override fun isWildcard(): Boolean = metaLogical.isWildcard + + override fun metaLogical(): MetaLogical = metaLogical + + override fun findRoot(): JoinableLogical = find() + + override fun setValue(newValue: T) { + if (_value !== newValue) { + this._value = newValue + notifyValueUpdated() + } + } + + override fun union(other: JoinableLogical, reconciler: JoinableLogical.ValueReconciler) { + val thisRepr = this.find() + val otherRepr = (other as LogicalImpl).find() + + // invariant: thisRepr.rank > otherRepr.rank + if (thisRepr.rank() < otherRepr.rank()) { + otherRepr.union(thisRepr, reconciler); + return; + + } else if (thisRepr.rank() == otherRepr.rank()) { + thisRepr.incRank(); + } + + val thisVal = thisRepr.value(); + val otherVal = otherRepr.value(); + + // first copy the value + if (thisVal == null && otherVal != null) { + // var ground + thisRepr.setValue(otherVal); + // TODO: clear the value in the "other" logical after union + + } else if (thisVal != null && otherVal == null) { + // ground var + // TODO: no need to copy the value + otherRepr.setValue(thisVal); + } + + // then set parent + otherRepr.setParent(thisRepr) + thisRepr.mergeParentObservers(otherRepr) + + // last, reconcile the values/merge observers + if (thisVal == null && otherVal == null) { + // var var + thisRepr.mergeValueObservers(otherRepr); + + } else if (thisVal != null && otherVal != null) { + // ground ground + reconciler.reconcile(thisVal, otherVal); + } + } + + override fun union(other: JoinableLogical) { + union(other, { a, b -> if (a != b) throw IllegalStateException("$a does not equal to $b")}) + } + + private fun find(): LogicalImpl { + val tmp = _parent + if (tmp == null) return this + else { + val root = tmp.find() + this._parent = root + return root + } + } + + private fun rank(): Int = rank + + private fun incRank() { rank++ } + + private fun setParent(parent: LogicalImpl) { + this._parent = parent + notifyParentUpdated() + } + + private fun mergeValueObservers(mergeFrom: JoinableLogical) { + val other = mergeFrom as LogicalImpl + valueObservers.addAll(other.valueObservers) + other.valueObservers.clear() + } + + private fun mergeParentObservers(mergeFrom: JoinableLogical) { + val other = mergeFrom as LogicalImpl + parentObservers.addAll(other.parentObservers) + other.parentObservers.clear() + } + + private fun notifyValueUpdated() { + val obs = ArrayList(valueObservers) + this.valueObservers.clear() + for (p in obs) { + p.second.valueUpdated(p.first) + } + } + + private fun notifyParentUpdated() { + val obs = ArrayList(parentObservers) + for (p in obs) { + p.second.parentUpdated(p.first) + } + } + + override fun toString(): String = + if (_parent != null) "${name}(^${_parent.toString()})" + else name + +} + +class DefaultMetaLogical (val name: String) : MetaLogical(name, Object::class.java as Class) {} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Matcher.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Matcher.kt new file mode 100644 index 00000000..2a19661e --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Matcher.kt @@ -0,0 +1,194 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.evaluation.MatchRule +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.logical.LogicalOwner +import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.program.Predicate +import jetbrains.mps.logic.reactor.program.Rule +import jetbrains.mps.logic.reactor.util.LazyIterable +import jetbrains.mps.logic.reactor.util.Profiler +import jetbrains.mps.unification.Substitution +import jetbrains.mps.unification.Term +import jetbrains.mps.unification.TermWrapper +import com.github.andrewoma.dexx.collection.List as PersList + +/** + * @author Fedor Isakov + */ + +class Matcher(val ruleIndex: RuleIndex, + val profiler: Profiler? = null) +{ + + private val rule2matchTrieSet = HashMap() + + fun matches(activeOcc: ConstraintOccurrence, aux: OccurrenceIndex) : Iterable = object : Iterable { + + override fun iterator(): Iterator { + + val relevantRules = ruleIndex.forOccurrence(activeOcc).toList() + + val tries = LazyIterable(relevantRules) { rule -> + rule2matchTrieSet[rule] ?: MatchTrieSet(rule, profiler).apply { + rule2matchTrieSet[rule] = this + } + }.asSequence() + + return tries.flatMap { matchTrieSet -> + matchTrieSet.matches(activeOcc, aux) + }.map { partialMatch -> + partialMatch.complete(profiler) + }.filter { match -> + match.successful + }.iterator() + } + + } + +} + +class Match(val rule: Rule, + val substitution: Substitution, + val keptOccurrences: List, + val discardedOccurrences: List) : MatchRule +{ + + val logicalContext: LogicalContext by lazy(LazyThreadSafetyMode.NONE) { + object : LogicalContext { + + // invariant: the variables in substitution bindings can only be instances of MetaLogical + val meta2subs = substitution.bindings().map { b -> + (b.`var`().symbol() as MetaLogical).to(b.term().toValue()) + }.toMap(HashMap, Any?>()) + + val meta2logical = HashMap, Logical<*>>() + + override fun variable(meta: MetaLogical): Logical = + (meta2logical[meta] ?: meta2subs[meta]?.let { value -> + when (value) { + is Logical<*> -> value + is LogicalOwner -> value.logical() + else -> LogicalImpl(value) + } + } ?: meta.logical().also { + logical -> meta2logical[meta] = logical }) as Logical + + } + } + + val patternPredicates: List by lazy(LazyThreadSafetyMode.NONE) { + (rule.headKept() + rule.headReplaced()).zip(keptOccurrences + discardedOccurrences).flatMap { p -> + p.first.patternPredicates(p.second.arguments()) }.toList() + } + + val successful: Boolean + get() = substitution.isSuccessful + + val isPropagation: Boolean + get() = !keptOccurrences.isEmpty() && discardedOccurrences.isEmpty() + + override fun rule(): Rule = rule + + override fun matchHeadKept(): Iterable = keptOccurrences + + override fun matchHeadReplaced(): Iterable = discardedOccurrences + +} + +/** Function term with arguments == constraints converted to terms. May contain variables. */ +class RuleTerm(rule: Rule) : + Function( + rule.tag(), + (rule.headKept() + rule.headReplaced()).map { c -> ConstraintPatternTerm(c) }) {} + +/** Function term with arguments == constraint arguments converted to terms. + * MetaLogical arguments are term variables. + * Everything else is either a term or a constant wrapping the value. */ +class ConstraintPatternTerm(constraint: Constraint) : + Function( + constraint.symbol(), + constraint.arguments().mapIndexed { idx: Int, arg: Any? -> arg.asTerm() }) {} + +/** Function term with arguments == terms corresponding to constraint occurrences. Never contains variables. */ +class MatchTerm(rule: Rule, kept: List, discarded: List) : + Function(rule.tag(), (kept + discarded).map { co -> ConstraintOccurrenceTerm(co) }) {} + +/** Function term with arguments == constraint occurrence arguments converted to terms. + * Logical arguments are constants wrapping the logical itself. + * Everything else is either a term or a constant wrapping the value. + * Never contains variable terms. */ +class ConstraintOccurrenceTerm(occurrence: ConstraintOccurrence) : + Function( + occurrence.constraint().symbol(), + occurrence.arguments().mapIndexed { idx: Int, arg: Any? -> arg.asTerm() }) {} + + +/** Wraps the terms for the internal representation of the unification algorithm. + * When unifying constraint and occurrence terms we are interested in bindings of the meta logicals, + * so these are represented as variables. + * In addition, the logicals are either represented as constants (unbound ones) or their values. + * When unwrapping, the reverse transformation takes place, so that the unification result binds meta logicals + * to logicals, and not to their values. */ +class MatchTermWrapper() : TermWrapper { + + override fun wrap(orig: Term): Term = + if (orig.`is`(Term.Kind.VAR)) { + val symbol = orig.symbol() + when (symbol) { + is MetaLogical<*> -> orig + is Logical<*> -> symbol.let { logical -> + if (logical.isBound && logical.findRoot().value() is Term) { + WrapGroundLogical(logical as Logical) + + } else { + WrapFreeLogical(logical) + } + } + else -> WrapConstant(orig) + } + + } else { + orig + } + + override fun unwrap(wrapper: Term): Term = when (wrapper) { + is WrapConstant -> wrapper.orig + is WrapFreeLogical -> Constant(wrapper.logical) + is WrapGroundLogical -> Constant(wrapper.logical) + else -> wrapper + } + +} + +fun Any?.asTerm(): Term = when (this) { + is MetaLogical<*> -> Variable(this) + is Logical<*> -> Variable(this) +// is Logical<*> -> if (this.isBound) this.findRoot().value().asTerm() else Constant(this.findRoot()) + is Term -> this // wrapped before unification + is Any -> Constant(this) + else -> throw NullPointerException() +} + +// FIXME: "unpacking" the logicals +fun Term.toValue(): Any? = if (this is Constant) this.symbol() else this + diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt new file mode 100644 index 00000000..f3029d23 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Occurrence.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.evaluation.EvaluationSession +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.program.Constraint + +/** + * @author Fedor Isakov + */ + +fun Constraint.occurrence(controller: Controller, arguments: List<*>, logicalContext: LogicalContext): ConstraintOccurrence = + Occurrence({ controller.currentFrame() }, this, arguments, logicalContext) + +private data class Occurrence (val currentFrame: () -> Frame, + val constraint: Constraint, + val arguments: List<*>, + val logicalContext: LogicalContext) : + ConstraintOccurrence, + LogicalObserver, + StoreItem +{ + override var alive = true + override var stored = false + + init { + for (a in arguments) { + if (a is Logical<*>) { + currentFrame().addObserver(a) { this } + } + } + } + + override fun constraint(): Constraint = constraint + + override fun arguments(): List<*> = arguments + + override fun logicalContext(): LogicalContext = logicalContext + + override fun valueUpdated(logical: Logical<*>) { + if (alive) { + (EvaluationSession.current() as SessionObjects).handler().reactivate(this) + } + } + + override fun parentUpdated(logical: Logical<*>) { + if (alive) { + (EvaluationSession.current() as SessionObjects).handler().reactivate(this) + } + } + + override fun terminate() { + for (a in arguments) { + if (a is Logical<*>) { + currentFrame().removeObserver(a) { this } + } + } + alive = false + } + + override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})" + +} + diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatch.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatch.kt new file mode 100644 index 00000000..851fe0b7 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatch.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.program.Rule +import jetbrains.mps.logic.reactor.util.Profiler +import jetbrains.mps.logic.reactor.util.profile +import jetbrains.mps.unification.Substitution +import jetbrains.mps.unification.Unification + +internal class PartialMatch(val rule: Rule) { + + internal lateinit var keptOccurrences: Array + private set + + internal lateinit var discardedOccurrences: Array + private set + + private lateinit var substitution: Substitution + + constructor(rule: Rule, keptSize: Int, discardedSize: Int) : + this(rule) + { + this.keptOccurrences = arrayOfNulls(keptSize) + this.discardedOccurrences = arrayOfNulls(discardedSize) + } + + + fun complete(profiler: Profiler? = null): Match { + return profiler.profile("matchesRule_${rule.tag()}") { + + val kept = keptOccurrences.asList() as List + val discarded = discardedOccurrences.asList() as List + + this.substitution = Unification.unify(MatchTerm(rule, kept, discarded), RuleTerm(rule), MatchTermWrapper()) + + if (substitution.isSuccessful) { + Match(rule, substitution, kept, discarded) + } + else + Match(rule, substitution, emptyList(), emptyList()) + + } + } + + internal fun keep(occ: ConstraintOccurrence, idx: Int): PartialMatch { + val match = PartialMatch(rule) + match.keptOccurrences = this.keptOccurrences.copyOf() + match.keptOccurrences[idx] = occ + match.discardedOccurrences = this.discardedOccurrences + return match + } + + internal fun discard(occ: ConstraintOccurrence, idx: Int): PartialMatch { + val match = PartialMatch(rule) + match.keptOccurrences = this.keptOccurrences + match.discardedOccurrences = this.discardedOccurrences.copyOf() + match.discardedOccurrences[idx] = occ + return match + } + + override fun toString(): String = "${keptOccurrences.toList()} \\ ${discardedOccurrences.toList()}" +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatchTrie.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatchTrie.kt new file mode 100644 index 00000000..f54a1fac --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PartialMatchTrie.kt @@ -0,0 +1,447 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.program.Rule +import jetbrains.mps.logic.reactor.util.Profiler +import jetbrains.mps.logic.reactor.util.profile +import jetbrains.mps.unification.Term +import jetbrains.mps.unification.Unification +import java.util.* +import kotlin.collections.ArrayList +import com.github.andrewoma.dexx.collection.Map as PersMap + +/** + * @author Fedor Isakov + */ +internal class MatchTrieSet(val rule: Rule, val profiler: Profiler?) { + + private val keptConstraints = rule.headKept().toCollection(ArrayList(4)) + + private val discardedConstraints = rule.headReplaced().toCollection(ArrayList(4)) + + // for each slot keep a set of "related" slots, that is the slots with matching (meta-)logicals + // this map is built only once per rule + private val relatedSlots: List = + HashMap, BitSet>().let { meta2slots -> + + (keptConstraints + discardedConstraints).forEachIndexed { slot, cst -> + cst.arguments().forEach { arg -> + if (arg is MetaLogical<*>) { + val bs = meta2slots[arg] ?: BitSet().apply { meta2slots[arg] = this } + bs.set(slot) + } + } + } + + ArrayList().apply { + (keptConstraints + discardedConstraints).forEachIndexed { slot, cst -> + val bs = BitSet() + cst.arguments().forEach { arg -> + if (arg is MetaLogical<*>) { + bs.or(meta2slots[arg]) + } + } + bs.clear(slot) + add(bs) + } + } + + } + + val tries = ArrayList() + + fun matches(activeOcc: ConstraintOccurrence, occIndex: OccurrenceIndex): Sequence { + val constraints = keptConstraints + discardedConstraints + + val relevantTries = ArrayList() + val copyTries = ArrayList() + for (t in tries) { + if (!t.vacant.isEmpty) { + copyTries.add(t) + for (idx in t.vacant.stream()) { + if (constraints[idx].probablyMatches(activeOcc)) { + relevantTries.add(PartialMatchTrie(activeOcc, occIndex, t)) + copyTries.removeAt(copyTries.size - 1) + break + } + } + } + } + if (relevantTries.isEmpty()) { + relevantTries.add(PartialMatchTrie(activeOcc, occIndex, null)) + } + + this.tries.clear() + this.tries.addAll(copyTries) + this.tries.addAll(relevantTries) + + return relevantTries.asSequence().flatMap { t -> t.matches() } + } + + inner class PartialMatchTrie(val activeOcc: ConstraintOccurrence, + val occIndex: OccurrenceIndex, + prototype: PartialMatchTrie?) + { + private val root: RootMatchTrieNode = RootMatchTrieNode(prototype?.root) + + val vacant = BitSet() + + fun matches(): Sequence = object : Sequence { + + override fun iterator() = object : Iterator { + + val firstRoot: MatchTrieNode? = root.firstChild + + var next: PartialMatch? = null + + override fun hasNext(): Boolean { + if (next == null) { + this.next = calcNext() + } + return (next != null) + } + + override fun next(): PartialMatch { + if (next == null) { + this.next = calcNext() + } + if (next != null) { + val tmp: PartialMatch = next!! + this.next = calcNext() + return tmp + } + throw NoSuchElementException() + } + + private fun calcNext(): PartialMatch? { + return profiler.profile("partialMatch_calcNext") { + // the search always starts at the first root + if (firstRoot == null) { + return null + } + var mtn: MatchTrieNode? = firstRoot + + while (mtn != null) { + if (mtn.isSeen) { + if (mtn.nextSibling == null) { + mtn.parent?.setSeen() + } + mtn = mtn.nextSibling ?: mtn.parent + + } else if (mtn.isLeaf) { + mtn.setSeen() + vacant.clear() + return mtn.match + + } else if (mtn.firstChild == null) { + mtn.setSeen() + vacant.or(mtn.vacantSlots) + vacant.andNot(mtn.relatedFirstVacantSlots) + + } else { + mtn = mtn.firstChild + } + } + + return null + } + } + } + + } + + private abstract inner class BaseMatchTrieNode() { + + var _firstChild: MatchTrieNode? = null + + protected fun copyChildren(copyFrom: BaseMatchTrieNode, parent: MatchTrieNode?) { + copyFrom._firstChild?.firstAlive()?.let { copyFirst -> + + val first = MatchTrieNode(parent, copyFirst) + var prev = first + var copyNext = copyFirst.nextSibling?.firstAlive() + + while (copyNext != null) { + val next = MatchTrieNode(parent, copyNext) + prev._nextSibling = next + prev = next + copyNext = copyNext.nextSibling?.firstAlive() + } + + this._firstChild = first + } + } + + protected fun buildNode(siblings: Iterable) : MatchTrieNode? { + var first: MatchTrieNode? = null + var prev: MatchTrieNode? = null + for (next in siblings) { + if (first == null) { + first = next + } + prev?._nextSibling = next + prev = next + } + + return first + } + + } + + private inner class RootMatchTrieNode(copyFrom: RootMatchTrieNode? = null) : BaseMatchTrieNode() { + + init { + if (copyFrom != null) { copyChildren(copyFrom, null) } + } + + val firstChild: MatchTrieNode? + get() { + if (_firstChild == null) { + this._firstChild = buildNode(calcRoots()) + } + return _firstChild + } + + private fun calcRoots(): Iterable { + return profiler.profile>("partialMatch_calcRoots") { + + keptConstraints.withIndex().filter {(_, cst) -> + cst.probablyMatches(activeOcc)}.map {(idx, cst) -> + MatchTrieNode(null, true, cst, activeOcc, idx) + } + + discardedConstraints.withIndex().filter {(_, cst) -> + cst.probablyMatches(activeOcc)}.map {(idx, cst) -> + MatchTrieNode(null, false, cst, activeOcc, idx) + } + + } + } + + } + + private inner class MatchTrieNode(val parent: MatchTrieNode?, + val keepConstraint: Boolean, + val constraint: Constraint, + val occurrence: ConstraintOccurrence, + val index: Int) : BaseMatchTrieNode() + { + + constructor(parent: MatchTrieNode?, copyFrom: MatchTrieNode) : + this(parent, copyFrom.keepConstraint, copyFrom.constraint, copyFrom.occurrence, copyFrom.index) + { + copyChildren(copyFrom, this) + } + + val firstChild: MatchTrieNode? + get() { + if (_firstChild == null) { + this._firstChild = buildNode(calcChildren()) + } + return _firstChild + } + + var _nextSibling: MatchTrieNode? = null + + val nextSibling: MatchTrieNode? + get() = _nextSibling + + private var _isSeen: Boolean = false + + val isSeen: Boolean + get() = _isSeen + + val vacantSlots: BitSet by lazy(LazyThreadSafetyMode.NONE) { + val result = BitSet() + result.set(0, keptConstraints.size + discardedConstraints.size) + foldPath(result) { bits, mtn -> + val slot = if (mtn.keepConstraint) mtn.index else mtn.index + keptConstraints.size + bits.clear(slot) + bits + } + result + } + + val relatedFirstVacantSlots: BitSet by lazy(LazyThreadSafetyMode.NONE) { + val result = vacantSlots.clone() as BitSet + result.and(relatedSlots[if (keepConstraint) index else index + keptConstraints.size]) + result + } + + val isLeaf: Boolean + get() = vacantSlots.isEmpty + + val match: PartialMatch by lazy(LazyThreadSafetyMode.NONE) { + val parentMatch = parent?.match ?: PartialMatch(rule, keptConstraints.size, discardedConstraints.size) + if (keepConstraint) parentMatch.keep(occurrence, index) + else parentMatch.discard(occurrence, index) + } + + fun setSeen() { + this._isSeen = true + } + + private fun calcChildren(): Iterable { + val nextSlots = if (!relatedFirstVacantSlots.isEmpty) relatedFirstVacantSlots else vacantSlots + val nextSlot = nextSlots.nextSetBit(0) + if (nextSlot < 0) { + return emptyList() + } + + val keep = nextSlot < keptConstraints.size + val nextIdx = if (keep) nextSlot else nextSlot - keptConstraints.size + val nextCst = if (keep) keptConstraints[nextSlot] else discardedConstraints[nextIdx] + + return if (nextCst.probablyMatches(activeOcc) && !hasOccurrence(activeOcc)) { + arrayListOf(MatchTrieNode(this, keep, nextCst, activeOcc, nextIdx)) + + } + else { + val (result, auxOccurrences) = lookupAuxOccurrences(nextCst) + when (result) { + // FIXME: this optimization is causing major problems, drop it? + Result.DEFINITIVE -> null /*parent*/ + Result.NONE -> this + Result.INCONCLUSIVE -> null + }?.forEachInPath { mtn -> mtn.setSeen() } + + auxOccurrences.map { auxOcc -> MatchTrieNode(this, keep, nextCst, auxOcc, nextIdx) } + } + } + + // the first component indicates the quality of the result: + // DEFINITIVE indicates the only possible matches were returned + // INCONCLUSIVE may yield some (ir-)relevant results + // NONE means no results could have been found at all, so it's useless to attempt again with the same input + private fun lookupAuxOccurrences(cst: Constraint): Pair> { + return profiler.profile>> ("lookupAux_${cst.symbol()}") { + + val fromArgs = ArrayList(4) + for (arg in metaInstances(cst.arguments()) + cst.arguments().filter { arg -> !(arg is MetaLogical<*>) }) { + when (arg) { + is Logical<*> -> fromArgs.addAll(occIndex.forLogicalAndConstraint(arg, cst)) + + is Term -> fromArgs.addAll(occIndex.forTermAndConstraint(arg, cst)) + + is Any -> fromArgs.addAll(occIndex.forValue(arg)) + } + } + if (fromArgs.isNotEmpty()) { + Result.DEFINITIVE.to(fromArgs.filter { occ -> cst.probablyMatches(occ) && !hasOccurrence(occ) }) + + } else { + return profiler.profile>> ("lookupAux_default_${cst.symbol()}") { + + val hasRelated = relatedFirstVacantSlots == vacantSlots + val allArgsMetaLogicals = cst.arguments().all { a -> a is MetaLogical<*> } + val noArgs = cst.arguments().isEmpty() + + if (noArgs || (allArgsMetaLogicals && !hasRelated)) { + Result.INCONCLUSIVE.to(occIndex.forSymbol(cst.symbol()).filter { occ -> cst.probablyMatches(occ) && !hasOccurrence(occ) }) + + } + else { + // all candidate occurrences should have been found by this time; if not, then there's none + Result.NONE.to(emptyList()) + } + } + } + } + } + + fun firstAlive(): MatchTrieNode? { + var fca: MatchTrieNode? = this + while (!(fca?.occurrence?.isAlive() ?: true)) { + fca = fca?.nextSibling + } + return fca + } + + + private fun collectMetaInstances(meta: Collection<*>, list: ArrayList): ArrayList { + constraint.arguments().zip(occurrence.arguments()).forEach { p -> + if (p.first is MetaLogical<*> && meta.contains(p.first) && !list.contains(p.second)) { + list.add(p.second!!) + } + } + return list + } + + private fun metaInstances(meta: Collection<*>): List = + foldPath(ArrayList(4)) { list, mtn -> mtn.collectMetaInstances(meta, list) } + + private fun hasOccurrence(occ: ConstraintOccurrence): Boolean = + anyInPath { mtn -> mtn.occurrence === occ } // referential equality! + + private inline fun anyInPath(predicate: (MatchTrieNode) -> Boolean): Boolean = + foldPath(false) { b, mtn -> b || predicate(mtn) } + + private inline fun forEachInPath(proc: (MatchTrieNode) -> Unit) = + foldPath(Unit) { b, mtn -> proc(mtn) } + + private inline fun foldPath(initVal: T, step: (T, MatchTrieNode) -> T): T { + var mtn: MatchTrieNode? = this + var currVal = initVal + while(mtn != null) { + currVal = step(currVal, mtn) + mtn = mtn.parent + } + return currVal + } + + } + + } + + fun Constraint.probablyMatches(occ: ConstraintOccurrence): Boolean { + if (this.symbol() != occ.constraint().symbol()) { return false } + + val cstArgIt = this.arguments().iterator() + val occArgIt = occ.arguments().iterator() + while (cstArgIt.hasNext() && occArgIt.hasNext()) { + val cstArg = cstArgIt.next() + val occArg = occArgIt.next() + + if (cstArg is Term && occArg is Term) { + if (!Unification.unify(cstArg, occArg).isSuccessful) { return false } + } + + if (cstArg !is MetaLogical<*> && occArg !is Logical<*>) { + if (!(cstArg?.equals(occArg) ?: (occArg === null))) { return false } + } + } + + return true + } + + enum class Result { + DEFINITIVE, + INCONCLUSIVE, + NONE + } + +} + + + + + + diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/PropagationHistory.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PropagationHistory.kt new file mode 100644 index 00000000..2eb0dac9 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/PropagationHistory.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import com.github.andrewoma.dexx.collection.ConsList +import com.github.andrewoma.dexx.collection.Maps +import com.github.andrewoma.dexx.collection.Map as PersMap +import com.github.andrewoma.dexx.collection.List as PersList +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.program.Rule +import jetbrains.mps.logic.reactor.util.IdWrapper + +internal class PropagationHistory { + + val recordedPropagation : PersMap>>> + + constructor() { + this.recordedPropagation = Maps.of>>> () + } + + private constructor(recorded : PersMap>>>) { + this.recordedPropagation = recorded + } + + fun isRecorded(m: Match): Boolean { + if (!m.isPropagation) return false + val test = m.keptOccurrences.map { occ -> IdWrapper(occ) }.sortedBy { idOcc -> idOcc.idHash }.toList() + + return recordedPropagation.get(m.rule)?.let { hist -> + hist.any { recorded -> recorded == test } // use the reference equality via IdWrapper + } ?: false + } + + fun record(pm: Match): PropagationHistory { + if (pm.isPropagation) { + val idOccs = pm.keptOccurrences.map { occ -> IdWrapper(occ) }.sortedBy { id -> id.idHash }.toList() + + val hist = recordedPropagation.get(pm.rule) ?: ConsList.empty>>() + + return PropagationHistory(recordedPropagation.put(pm.rule, hist.prepend(idOccs))) + } + return this + } + +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt new file mode 100644 index 00000000..cdb01fbf --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleIndex.kt @@ -0,0 +1,191 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.program.ConstraintSymbol +import jetbrains.mps.logic.reactor.program.Handler +import jetbrains.mps.logic.reactor.program.Rule +import jetbrains.mps.unification.Term +import java.util.* + +/** + * @author Fedor Isakov + */ + +class RuleIndex : Iterable { + + private val primarySymbol2valueIndex = HashMap() + + private val auxSymbol2valueIndex = HashMap() + + private val tag2rule = LinkedHashMap() + + constructor(handlers: Iterable) { + for (h in handlers) { + for (r in h.rules()) { + tag2rule[r.tag()] = r + } + } + buildIndex(handlers) + } + + fun byTag(tag: String): Rule? = tag2rule[tag] + + fun forOccurrence(occ: ConstraintOccurrence): Iterable = + primarySymbol2valueIndex.get(occ.constraint().symbol())?.select(occ) ?: + (auxSymbol2valueIndex.get(occ.constraint().symbol())?.select(occ) ?: emptyList()) + + override fun iterator(): Iterator = tag2rule.values.iterator() + + private fun buildIndex(handlers: Iterable) { + // first, init the primary symbols value index + handlers + .flatMap { h -> h.primarySymbols() } + .forEach { symbol -> primarySymbol2valueIndex.getOrPut(symbol) { ValueIndex(symbol) } } + + for (h in handlers) { + val primSymbols = h.primarySymbols().toSet() + for (r in h.rules()) { + for (c in (r.headKept() + r.headReplaced())) { + val symbol = c.symbol() + if (symbol in primSymbols) { + primarySymbol2valueIndex[symbol]?.update(r, c) + + } else { + auxSymbol2valueIndex.getOrPut(symbol) { ValueIndex(symbol) }.update(r, c) + } + } + } + } + } + +} + +/** + * Represents a list of rules with at least one constraint matching the 'symbol'. + * The list is indexed by all values extracted from the constraint arguments. + * The list can be selected based on the argument(s) of a constraint occurrence. + */ +private class ValueIndex(val symbol: ConstraintSymbol) { + + val rules = ArrayList() + + val anySelectors = ArrayList>() + + val termSelectors = ArrayList>() + + val wildcardSelectors = ArrayList() + + init { + for (idx in 1..symbol.arity()) { + anySelectors.add(HashMap()) + termSelectors.add(TermTrie()) + wildcardSelectors.add(BitSet()) + } + } + + fun update(rule: Rule, cst: Constraint) { + var bit = rules.indexOf(rule) + if (bit < 0) { + bit = rules.size + rules.add(rule) + } + + for ((idx, arg) in cst.arguments().withIndex()) { + val value2indices = anySelectors[idx] + when (arg) { + is MetaLogical<*> -> { + // all values should be accepted by a meta logical + wildcardSelectors[idx].set(bit) + } + is Term -> { + termSelectors.set(idx, termSelectors[idx].put(arg, bit)) + } + is Any -> { + val bitset = value2indices.getOrPut(arg) { BitSet() } + bitset.set(bit) + } + else -> { + /* never happens */ + throw NullPointerException() + } + } + } + } + + fun select(occ: ConstraintOccurrence): Iterable { + if (occ.constraint().symbol() != symbol) throw IllegalArgumentException() + + // initially select all rules + val upToBit = rules.size + val ruleIndices = BitSet(rules.size) + ruleIndices.set(0, upToBit) + + for ((idx, arg) in occ.arguments().withIndex()) { + val value2indices = anySelectors[idx] + val termIndices = termSelectors[idx] + val wildcardIndices = wildcardSelectors[idx] + if (arg is Logical<*> && !arg.isBound) { + // ALL values must be selected for a free logical + continue + } + + val argVal = if (arg is Logical<*>) arg.findRoot().value() else arg + when (argVal) { + is Term -> { + val bits = wildcardIndices.clone() as BitSet + for (i in termIndices.lookupValues(argVal)) { + bits.set(i) + } + ruleIndices.and(bits) + } + is Any -> { + if (value2indices.containsKey(argVal)) { + // ensure only rules with either matching values or wildcard arguments get selected + val bits = wildcardIndices.clone() as BitSet + bits.or(value2indices[argVal]) + ruleIndices.and(bits) + + } else { + ruleIndices.and(wildcardIndices) + } + } + else -> { + /* never happens */ + throw NullPointerException() + } + } + + if (ruleIndices.isEmpty) { + break + } + } + + val result = ArrayList(ruleIndices.cardinality()) + var next = ruleIndices.nextSetBit(0) + while (next >= 0) { + result.add(rules.get(next)) + next = ruleIndices.nextSetBit(next+1) + } + return result + } + +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Store.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Store.kt new file mode 100644 index 00000000..63af9e79 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Store.kt @@ -0,0 +1,333 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import com.github.andrewoma.dexx.collection.DerivedKeyHashMap +import com.github.andrewoma.dexx.collection.KeyFunction +import com.github.andrewoma.dexx.collection.Maps +import com.github.andrewoma.dexx.collection.internal.base.AbstractSet +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.evaluation.EvaluationSession +import jetbrains.mps.logic.reactor.evaluation.StoreView +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.program.ConstraintSymbol +import jetbrains.mps.unification.Term +import jetbrains.mps.logic.reactor.util.* +import java.util.* +import com.github.andrewoma.dexx.collection.Map as PersMap +import com.github.andrewoma.dexx.collection.Set as PersSet +import com.github.andrewoma.dexx.collection.Vector as PersVector + +/** + * @author Fedor Isakov + */ + +fun ConstraintOccurrence.isStored(): Boolean = + // TODO: superfluous cast + (this as StoreItem).stored + +fun ConstraintOccurrence.isAlive(): Boolean = + // TODO: superfluous cast + (this as StoreItem).alive + +interface StoreItem { + + var alive: Boolean + + var stored: Boolean + + fun terminate(): Unit + +} + +interface StoreKeeper { + + fun store(): Store + + fun addObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) + + fun removeObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) + +} + +interface OccurrenceIndex { + + fun forSymbol(symbol: ConstraintSymbol): Iterable + + fun forLogical(logical: Logical<*>): Iterable + + fun forLogicalAndConstraint(logical: Logical<*>, cst:Constraint): Iterable + + fun forTerm(term: Term): Iterable + + fun forTermAndConstraint(term: Term, cst: Constraint): Iterable + + fun forValue(value: Any): Iterable + +} + +/** + * TODO: make this class persistent. + */ +class Store : LogicalObserver, OccurrenceIndex { + + val currentFrame: () -> StoreKeeper + + var symbol2occurrences: PersMap> + + var logical2occurrences: PersMap>, IdHashSet> + + var term2occurrences: TermTrie + + var value2occurrences: PersMap> + + constructor(copyFrom: Store, currentFrame: () -> StoreKeeper) { + this.currentFrame = currentFrame + this.symbol2occurrences = copyFrom.symbol2occurrences + this.logical2occurrences = copyFrom.logical2occurrences + this.term2occurrences = copyFrom.term2occurrences + this.value2occurrences = copyFrom.value2occurrences + } + + constructor(copyFrom: StoreView, currentFrame: () -> StoreKeeper) { + this.currentFrame = currentFrame + this.symbol2occurrences = copyFrom.constraintSymbols() + .fold(Maps.of()) { map, sym -> map.put(sym, IdHashSet(copyFrom.occurrences(sym))) } + + var l2o = Maps.of>, IdHashSet>() + var t2o = TermTrie() + var v2o = Maps.of>() + + copyFrom.allOccurrences().forEach { occ -> + occ.arguments().forEach { arg -> + when (arg) { + is Logical<*> -> l2o = l2o.put(IdWrapper(arg.findRoot()), l2o[IdWrapper(arg.findRoot())]?.add(occ) ?: singletonSet(occ)) + is Term -> t2o = t2o.put(arg.withConstraint(occ.constraint()), occ) + is Any -> v2o = v2o.put(arg, v2o[arg]?.add(occ) ?: singletonSet(occ)) + } + } + + } + + this.logical2occurrences = l2o + this.term2occurrences = t2o + this.value2occurrences = v2o + } + + constructor(currentFrame: () -> StoreKeeper) { + this.currentFrame = currentFrame + this.symbol2occurrences = Maps.of() + this.logical2occurrences = Maps.of() + this.term2occurrences = TermTrie() + this.value2occurrences = Maps.of() + } + + override fun valueUpdated(logical: Logical<*>) { + logical2occurrences[IdWrapper(logical.findRoot())]?.let { toMerge -> + val value = logical.findRoot().value() + when (value) { + is Term -> { + for (occ in toMerge) { + this.term2occurrences = term2occurrences.put(value.withConstraint(occ.constraint()), occ) + } + } + is Any -> { + var newSet = value2occurrences[value] ?: emptySet() + for (occ in toMerge) { + newSet = newSet.add(occ) + } + this.value2occurrences = value2occurrences.put(value, newSet) + } + else -> { + // never happens + throw NullPointerException() + } + } + } + } + + override fun parentUpdated(logical: Logical<*>) { + // TODO: should we care about the order in which occurrences are stored? + val logicalId = IdWrapper(logical) + logical2occurrences[logicalId]?.let { toMerge -> + val rootId = IdWrapper(logical.findRoot()) + var newSet = logical2occurrences[rootId] ?: emptySet() + for (log in toMerge) { + newSet = newSet.add(log) + } + this.logical2occurrences = logical2occurrences.remove(logicalId).put(rootId, newSet) + assert(logical2occurrences.containsKey(rootId)) + assert(!logical2occurrences.containsKey(logicalId)) + } + } + + fun store(occ: ConstraintOccurrence): Unit { + val symbol = occ.constraint().symbol() + + this.symbol2occurrences = symbol2occurrences.put(symbol, + symbol2occurrences[symbol]?.add(occ) ?: singletonSet(occ)) + + for (arg in occ.arguments()) { + val value = if (arg is Logical<*> && arg.isBound) arg.findRoot().value() else arg + when (value) { + is Logical<*> -> { + // free logical + val argId = IdWrapper(value.findRoot()) + this.logical2occurrences = logical2occurrences.put(argId, + logical2occurrences[argId]?.add(occ) ?: singletonSet(occ)) + currentFrame().addObserver(value) { frame -> frame.store() } + } + is Term -> { + this.term2occurrences = term2occurrences.put(value.withConstraint(occ.constraint()), occ) + } + is Any -> { + this.value2occurrences = value2occurrences.put(value, + value2occurrences[value]?.add(occ) ?: singletonSet(occ)) + } + else -> { + // never happens + throw NullPointerException() + } + } + } + + // TODO: superfluous cast + (occ as StoreItem).stored = true + } + + fun discard(occ: ConstraintOccurrence, profiler: Profiler? = null, tag: String? = null): Unit { + val symbol = occ.constraint().symbol() + + symbol2occurrences[symbol]?.remove(occ)?.let { newList -> + this.symbol2occurrences = symbol2occurrences.put(symbol, newList) + } + + for (arg in occ.arguments()) { + when (arg) { + is Logical<*> -> { + val argId = IdWrapper(arg.findRoot()) + logical2occurrences[argId]?.remove(occ)?.let { newList -> + this.logical2occurrences = if (newList.isEmpty) { + logical2occurrences.remove(argId) + + } else { + logical2occurrences.put(argId, newList) + } + } + } + is Term -> { + // removing occurrences from the index is *very* expensive, + // so let's simply use the 'alive' flag to filter out terminated occurrences +// this.term2occurrences = term2occurrences.remove(arg, occ) + } + is Any -> { + value2occurrences[arg]?.remove(occ)?. let { newList -> + this.value2occurrences = value2occurrences.put(arg, newList) + } + } + } + } + + // TODO: superfluous cast + (occ as StoreItem).stored = false + occ.terminate() + } + + fun allOccurrences(): Sequence { + return symbol2occurrences.values().flatten().filter { co -> co.isStored() }.asSequence() + } + + fun view(): StoreView = StoreViewImpl(allOccurrences()) + + override fun forSymbol(symbol: ConstraintSymbol): Iterable { + return (symbol2occurrences[symbol] ?: emptySet()).filter { co -> co.isStored() } + } + + override fun forLogicalAndConstraint(logical: Logical<*>, cst: Constraint): Iterable { + return if (logical.isBound) { + val value = logical.findRoot().value() + when (value) { + is Term -> forTermAndConstraint(value, cst) + is Any -> forValue(value) + else -> throw NullPointerException() + } + + } else { + (logical2occurrences[IdWrapper(logical.findRoot())] ?: emptySet()).filter { co -> co.isStored() } + } + } + + override fun forLogical(logical: Logical<*>): Iterable { + return if (logical.isBound) { + val value = logical.findRoot().value() + when (value) { + is Term -> forTerm(value) + is Any -> forValue(value) + else -> throw NullPointerException() + } + + } else { + (logical2occurrences[IdWrapper(logical.findRoot())] ?: emptySet()).filter { co -> co.isStored() } + } + } + + override fun forTerm(term: Term): Iterable { + return term2occurrences.lookupValues(term.withAny()).filter { it.isStored() } + } + + override fun forTermAndConstraint(term: Term, cst: Constraint): Iterable { + return term2occurrences.lookupValues(term.withConstraint(cst)).filter { it.isStored() } + } + + override fun forValue(value: Any): Iterable { + return (value2occurrences[value] ?: emptySet()).filter { co -> co.isStored() } + } + + private fun Term.withConstraint(cst: Constraint): Term = Function(CONSTRAINT, listOf(Function(cst.symbol(), emptyList()), this)) + + private fun Term.withAny(): Term = Function(CONSTRAINT, listOf(Variable(ANY), this)) + + private companion object { + + private val CONSTRAINT = object : Any() { + override fun toString(): String = "CONSTRAINT" + } + + private val ANY = object : Any() { + override fun toString(): String = "ANY" + } + + } + +} + +private class StoreViewImpl(occurrences: Sequence) : StoreView { + + val allOccurrences = occurrences.toSet() + + val allSymbols = allOccurrences.map { co -> co.constraint().symbol() }.toSet() + + override fun constraintSymbols(): Iterable = allSymbols + + override fun allOccurrences(): Iterable = allOccurrences + + override fun occurrences(symbol: ConstraintSymbol): Iterable = + allOccurrences.filter { co -> co.constraint().symbol() == symbol }.toSet() + +} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermImpl.kt new file mode 100644 index 00000000..dc7be155 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermImpl.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.unification.Term + +abstract class TermImpl(val symbol: Any) : Term { + + override fun symbol() = symbol + + override fun compareTo(other: Term): Int { + return if (this.javaClass === other.javaClass) + symbol.toString().compareTo(symbol.toString()) + else + this.javaClass.toString().compareTo(other.javaClass.toString()) + } +} + +open class Function(symbol: Any, val arguments: List) : TermImpl(symbol) { + + override fun arguments(): Collection = arguments + + override fun `is`(kind: Term.Kind?): Boolean = (kind === Term.Kind.FUN) + + override fun get(): Term = this +} + +class Constant(symbol: Any) : Function(symbol, emptyList()) {} + +class WrapConstant(val orig: Term) : Function(orig, emptyList()) {} + +class WrapFreeLogical(val logical: Logical<*>) : Function(logical.findRoot(), emptyList()) {} + +class WrapGroundLogical(val logical: Logical) : + Function(logical.findRoot().value().symbol(), + logical.findRoot().value().arguments().toList()) {} + +class Variable(symbol: Any) : TermImpl(symbol) { + + override fun arguments(): Collection = emptyList() + + override fun `is`(kind: Term.Kind): Boolean = (kind === Term.Kind.VAR) + + override fun get(): Term = TODO() +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermTrie.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermTrie.kt new file mode 100644 index 00000000..6780fdaf --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/TermTrie.kt @@ -0,0 +1,269 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.core + +import com.github.andrewoma.dexx.collection.ConsList +import com.github.andrewoma.dexx.collection.Map as PersMap +import com.github.andrewoma.dexx.collection.Maps +import jetbrains.mps.logic.reactor.util.* +import jetbrains.mps.unification.Term +import java.util.* + +/** + * @author Fedor Isakov + */ + +/** + * A persistent map-like structure to keep multiple values associated with a single term. + * + * The method `put` returns a new instance updated to contain the new association. Likewise, the method + * `remove` returns the new instance updated to remove the association. + * + * Term variables are supported: a variable matches any term, including other variable. All variables are treated + * as "wildcards", so the subsequent unification of the query term and the key does not necessarily succeeds. + * + * To access the stored values, the method `lookupValues` returns all values associated with terms. + * The method `allValues` returns all stored values. Neither method makes any guarantees about the cardinality or + * the order of the values returned. + * + * The implementation is based on the structure called "discrimination tree" as described in the chapter 26 of [1]. + * + * [1] Alan Robinson and Andrei Voronkov (Eds.). 2001. Handbook of Automated Reasoning. + * Elsevier Sci. Pub. B. V., Amsterdam, The Netherlands, The Netherlands. + * + */ +class TermTrie() { + + private companion object { + + private val WILDCARD = object : Any() { + override fun toString() = "WILDCARD" + } + + } + + private var root: PathNode + + init { + root = PathNode(WILDCARD, 1) + } + + private constructor(setRoot: PathNode) : this() { + this.root = setRoot + } + + fun put(term: Term, value: T): TermTrie = TermTrie(putValue(term, value)) + + fun remove(term: Term, value: T): TermTrie = TermTrie(removeValue(term, value)) + + fun lookupValues(term: Term): Iterable { + val result = ArrayList() + visitMatching(term) { value -> result.add(value) } + return result + } + + fun allValues(): Iterable { + val result = ArrayList() + visitAll(root, { value -> result.add(value) }) + return result + } + + private fun putValue(matchTerm: Term, value: T): PathNode { + val seen = IdentityHashMap() + var nodeStack: ConsList> = cons(root) + val termList = arrayListOf(matchTerm) + + while (!termList.isEmpty()) { + val node = nodeStack.first() !! + val term = termList.removeAt(termList.size - 1) + + // dereferece the term only if it hasn't been dereferenced before + val deref = deref(term).let { dt -> seen[dt]?.run { term } ?: dt.apply { seen[dt] = term } } + val arguments = deref.arguments().toList() + for(i in 1..arguments.size) { + termList.add(arguments[arguments.size - i]) + } + + val nextNode = node.nextOrDefault(symbolOrWildcard(deref)) { sym -> PathNode(sym, arguments.size) } + nodeStack = nodeStack.prepend(nextNode) + } + + val head = nodeStack.first() !! + return nodeStack.tail().fold(head.addValue(value)) { nextNode, node -> node.putNext(nextNode) } + } + + private fun removeValue(matchTerm: Term, value: T): PathNode { + val seen = IdentityHashMap() + var nodeStack: ConsList> = cons(root) + val termList = arrayListOf(matchTerm) + + while (!termList.isEmpty()) { + val node = nodeStack.first() !! + val term = termList.removeAt(termList.size - 1) + + // dereferece the term only if it hasn't been dereferenced before + val deref = deref(term).let { dt -> seen[dt]?.run { term } ?: dt.apply { seen[dt] = term } } + val arguments = deref.arguments().toList() + for(i in 1..arguments.size) { + termList.add(arguments[arguments.size - i]) + } + + val nextNode = node.next(symbolOrWildcard(deref)) + if (nextNode == null) { + // term not found + return root + } + nodeStack = nodeStack.prepend(nextNode) + } + + val head = nodeStack.first() !! + val newHead = head.removeValue(value) + return if (head !== newHead) { + nodeStack.tail().fold(newHead) { nextNode, node -> node.putNext(nextNode) } + } else { + // value not found + root + } + } + + private fun visitMatching(matchTerm: Term, visitor: (T) -> Unit) { + val seen = IdentityHashMap() + val visitList = arrayListOf(root.to(cons(matchTerm))) + + while (!visitList.isEmpty()) { + val (node, termList) = visitList.removeAt(visitList.size - 1) + if (!termList.isEmpty) { + val term = termList.first() !! + val termTail = termList.tail() + + // dereferece the term only if it hasn't been dereferenced before + val deref = deref(term).let { dt -> seen[dt]?.run { term } ?: dt.apply { seen[dt] = term } } + + val sym = symbolOrWildcard(deref) + if (sym == WILDCARD) { + if (!termTail.isEmpty) { + node.skipAllNext().forEach { n -> visitList.add(n.to(termTail)) } + + } else { + node.allNext().forEach { visitAll(it, visitor) } + } + + } else { + node.next(WILDCARD)?.let { nn -> + nn.values().forEach(visitor) + if (!termTail.isEmpty) { + visitList.add(nn.to(termTail)) + } + } + node.next(sym)?.let { nn -> + nn.values().forEach(visitor) + + var newTail = termTail + val arguments = deref.arguments().toList() + for(i in 1..arguments.size) { + newTail = newTail.prepend(arguments[arguments.size - i]) + } + + if (!newTail.isEmpty) { + visitList.add(nn.to(newTail)) + } + } + } + } + } + } + + private fun visitAll(node: PathNode, visitor: (T) -> Unit) { + node.values().forEach(visitor) + node.allNext().forEach { visitAll(it, visitor) } + } + + private fun deref(term: Term): Term { + var deref = term + while (deref.`is`(Term.Kind.REF)) { + deref = deref.get() + } + return deref + } + + private fun symbolOrWildcard(term: Term): Any { + return if (term.`is`(Term.Kind.VAR)) { + WILDCARD + + } else { + term.symbol() + } + } + + private class PathNode(val symbol: Any, + val arity: Int, + val next: PersMap>, + val values: IdHashSet) + { + + constructor(symbol: Any, arity: Int) : + this(symbol, arity, Maps.of(), emptySet()) + + constructor(copyFrom: PathNode, setValues: IdHashSet) : + this(copyFrom.symbol, copyFrom.arity, copyFrom.next, setValues) + + constructor(copyFrom: PathNode, setNext: PersMap>) : + this(copyFrom.symbol, copyFrom.arity, setNext, copyFrom.values) + + fun values(): Iterable = values + + fun hasValues(): Boolean = !values.isEmpty + + fun hasValue(value: T): Boolean = values.contains(value) + + fun next(symbol: Any): PathNode? = next[symbol] + + fun hasNext(): Boolean = !next.isEmpty + + fun allNext(): Iterable> = next.values() + + fun skipAllNext(): Iterable> = allNext(0) + + private fun allNext(skip: Int): Iterable> = allNext().flatMap { nn -> + val newSkip = skip + nn.arity + if (newSkip == 0) { + listOf(nn) + + } else { + nn.allNext(newSkip - 1) + } + } + + fun putNext(node: PathNode): PathNode = PathNode(this, next.put(node.symbol, node)) + + fun removeNext(node: PathNode): PathNode = PathNode(this, next.remove(node.symbol)) + + fun addValue(value: T): PathNode = PathNode(this, values.add(value)) + + fun removeValue(value: T): PathNode = + if (values.contains(value)) PathNode(this, values.remove(value)) else this + + inline fun nextOrDefault(symbol: Any, + default: (sym: Any) -> PathNode): PathNode + { + return next[symbol] ?: default(symbol) + } + + } + + +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/util/DexxCollections.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/util/DexxCollections.kt new file mode 100644 index 00000000..0671c89f --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/util/DexxCollections.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.util + +import com.github.andrewoma.dexx.collection.ConsList + +/** + * @author Fedor Isakov + */ + + +fun emptyConsList(): ConsList = ConsList.empty() + +fun cons(e: E): ConsList = emptyConsList().append(e) + +fun consListOf(vararg args: E): ConsList { + val builder = ConsList.factory().newBuilder() + for (e in args) { + builder.add(e) + } + return builder.build() +} + +fun ConsList.removeAt(idx: Int): ConsList { + if (idx < 0) throw IllegalArgumentException("index < 0") + val left = this.take(idx) + var right = this.drop(idx + 1) + for (e in left.reversed()) { + right = right.prepend(e) + } + return right +} + +fun ConsList?.remove(e: E): ConsList? { + return this?.run { + val idx = indexOf(e) + if (idx >= 0) removeAt(idx) else this + } +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/util/IdHashSet.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/util/IdHashSet.kt new file mode 100644 index 00000000..aa972176 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/util/IdHashSet.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.util + +import com.github.andrewoma.dexx.collection.DerivedKeyHashMap +import com.github.andrewoma.dexx.collection.KeyFunction +import com.github.andrewoma.dexx.collection.internal.base.AbstractSet + +class IdHashSet : AbstractSet { + + companion object { + + val EMPTY_SET = IdHashSet() + + private fun idkey(value: E) = System.identityHashCode(value) + + } + + var storage: DerivedKeyHashMap + + constructor() { + this.storage = emptyStorage() + } + + constructor(storage: DerivedKeyHashMap) { + this.storage = storage + } + + constructor(copyFrom: Iterable) { + this.storage = copyFrom.fold(emptyStorage()) { s, e -> s.put(idkey(e), e) } + } + + override fun add(value: E): IdHashSet = IdHashSet(storage.put(idkey(value), value)) + + override fun contains(value: E): Boolean = storage.containsKey(idkey(value)) + + override fun remove(value: E): IdHashSet = IdHashSet(storage.remove(idkey(value))) + + override fun iterator(): MutableIterator = object: MutableIterator { + + val storageIt = storage.iterator() + + override fun hasNext(): Boolean = storageIt.hasNext() + + override fun next(): E = storageIt.next().component2() + + override fun remove() { + throw UnsupportedOperationException() + } + } + + private fun emptyStorage(): DerivedKeyHashMap { + return DerivedKeyHashMap(object : KeyFunction { + override fun key(value: E): Int = idkey(value) + }) + } + +} + +@Suppress("UNCHECKED_CAST") +fun emptySet(): IdHashSet = IdHashSet.EMPTY_SET as IdHashSet + +fun singletonSet(e: E): IdHashSet = emptySet().add(e) \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/util/IdWrapper.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/util/IdWrapper.kt new file mode 100644 index 00000000..d4fe3c19 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/util/IdWrapper.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.util + +class IdWrapper(val wrapped: T) { + + val idHash = System.identityHashCode(wrapped) + + override fun hashCode(): Int = idHash + + override fun equals(other: Any?): Boolean { + if (other is IdWrapper<*>) + return this.wrapped === other.wrapped // referential equality! + return false + } + + override fun toString(): String = "${wrapped.toString()} #$idHash" + +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/util/Iterables.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/util/Iterables.kt new file mode 100644 index 00000000..081c8fef --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/util/Iterables.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.util + +import java.util.* + +/** + * @author Fedor Isakov + */ + +class LazyIterable(val origin: List, val map: (S) -> T) : Iterable { + + val mapped = arrayOfNulls(origin.size) + + override fun iterator(): Iterator = object: Iterator { + + var idx: Int = 0 + + override fun hasNext(): Boolean = idx < origin.size + + override fun next(): T { + if (idx < origin.size && mapped[idx] == null) { + mapped[idx] = map(origin[idx]) + } + return if (idx < origin.size) mapped[idx++] as T else throw NoSuchElementException() + } + } +} diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/util/Profiler.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/util/Profiler.kt new file mode 100644 index 00000000..0aadd932 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/util/Profiler.kt @@ -0,0 +1,143 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.logic.reactor.util + +import java.util.* + +/** + * @author Fedor Isakov + */ + +class Profiler { + + companion object { + private var lastTokenId = 0 + } + + private val tokenStack = LinkedList() + + constructor() { + tokenStack.push(Token("_", ++lastTokenId)) + } + + fun start(name: String): Token { + val tok = Token(name, ++lastTokenId) + tokenStack.peek().addChild(tok) + tokenStack.push(tok) + return tok + } + + fun end(tok: Token? = null) { + val pop = tokenStack.pop() + if (tok != null && pop !== tok) throw IllegalStateException("wrong token") + pop.end() + } + + // name -> (duration, freq) + fun rawProfilingData(): Map { + val name2duration = HashMap(1000) + tokenStack.peek().mergeDurations(name2duration) + return name2duration + } + + fun formattedData(): Map { + val entries = rawProfilingData().entries.toMutableList() + val sum = entries.asSequence().fold(0L) { acc, e -> acc + (e.value.dur/1000000) } + var toReport = (sum * 0.98).toLong() + // sort in-pace descending + Collections.sort(entries) { e1, e2 -> if (e2.value.dur < e1.value.dur) -1 else 1 } + return entries.asSequence().takeWhile { e -> toReport > 0 && e.value.dur / 1000000 > 0}.map { e -> + val millis= e.value.dur / 1000000 + toReport -= millis + + val parents = e.value.parents.entries.toMutableList() + //sort in-place descending + Collections.sort(parents) { e1, e2 -> e2.value - e1.value } + val sb = StringBuilder("%1\$Ts.%1\$TLs (%2\$d times)".format(millis, e.value.freq)) + parents.forEach { e -> sb.append("\n -- ${e.key} (${e.value} times)") } + + e.key.to(sb.toString()) + }.toMap() + } + + fun clear() { + tokenStack.clear() + } + +} + +class Token(val name: String, val id: Int) { + + val startNano: Long = System.nanoTime() + + var endNano: Long? = null + private set + + private val children = ArrayList() + + fun addChild(ch: Token) { + children.add(ch) + } + + fun end() { + if (endNano != null) throw IllegalStateException("already ended") + this.endNano = System.nanoTime() + } + + private fun duration(): Long? = endNano?.minus(startNano) + + private fun ownDuration(): Long? = duration()?.minus(children.map { ch -> ch.duration() ?: 0 }.sum()) + + fun mergeDurations(name2durFreq: MutableMap) { + children.forEach { ch -> + val durFreq = name2durFreq.getOrPut(ch.name) { DurFreq() } + ch.ownDuration()?.let { dur -> durFreq.dur += dur } + durFreq.freq += 1 + durFreq.parents.set(name, 1 + durFreq.parents.getOrElse(name) {0}) + } + for (c in children) { + c.mergeDurations(name2durFreq) + } + } + +} + +class DurFreq { + var dur: Long = 0L + var freq: Int = 0 + val parents: MutableMap = HashMap() +} + +inline fun Profiler?.profile(name: String, proc: () -> Unit): Unit { + val tok = this?.start(name) + try { + proc() + } + finally { + this?.end(tok) + } +} + +inline fun Profiler?.profile(name: String, function: () -> R): R { + val tok = this?.start(name) + try { + return function() + } + finally { + this?.end(tok) + } +} diff --git a/reactor/Core/src/jetbrains/mps/unification/Substitution.java b/reactor/Core/src/jetbrains/mps/unification/Substitution.java new file mode 100644 index 00000000..b45b282d --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/unification/Substitution.java @@ -0,0 +1,142 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +/** + * The result of unification, either successful or not. If successful, contains a read-only collection + * of {@link Binding} instances. The result is presented in a "triangular form", which means the bindings may + * transitively bind variables. No cycles are allowed in the returned bindings. + * + * @author Fedor Isakov + */ +public class Substitution { + + private boolean mySuccessful; + + private Failure myFailure; + + public Substitution(boolean successful) { + mySuccessful = successful; + } + + public Substitution(FailureCause failCause) { + myFailure = new Failure(failCause); + mySuccessful = false; + } + + public Substitution(FailureCause failCause, Object... details) { + myFailure = new Failure(failCause, details); + mySuccessful = false; + } + + public boolean isSuccessful() { + return mySuccessful; + } + + public Collection bindings() { + return Collections.emptyList(); + } + + public FailureCause failureCause() { + return myFailure != null ? myFailure.getCause() : null; + } + + public Object[] failureDetails() { + return myFailure != null ? myFailure.getDetails() : null; + } + + public String toString() { + return myFailure != null ? "[" + String.valueOf(myFailure) + "]" : "[FAILED_SUBSTITUTION]"; + } + + /** + * Represents a variable binding in case of a successful unification. + * A variable may be bound to either a function term or another variable. + */ + public static class Binding { + + private Term myVar; + + private Term myTerm; + + public Binding(Term myVar, Term myTerm) { + this.myVar = myVar; + this.myTerm = myTerm; + } + + public Term var() { + return myVar; + } + + public Term term() { + return myTerm; + } + } + + + public static class Failure { + + private FailureCause myCause; + + private Object[] myDetails; + + public Failure(FailureCause cause) { + this.myCause = cause; + } + + public Failure(FailureCause cause, Object... details) { + this.myCause = cause; + this.myDetails = details; + } + + public FailureCause getCause() { + return myCause; + } + + public Object[] getDetails() { + return myDetails; + } + + @Override + public String toString() { + return String.valueOf(myCause) + (myDetails != null ? Arrays.asList(myDetails) : ""); + } + } + + public enum FailureCause { + CYCLE_DETECTED("cycle detected"), + UNRECONCILED_REF("unreconciled ref"), + SYMBOL_CLASH("symbol clash"), + UKNOWN("uknown"); + + private String myMessage; + + FailureCause(String message) { + myMessage = message; + } + + @Override + public String toString() { + return myMessage; + } + } + +} diff --git a/reactor/Core/src/jetbrains/mps/unification/Term.java b/reactor/Core/src/jetbrains/mps/unification/Term.java new file mode 100644 index 00000000..931a299e --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/unification/Term.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification; + +import java.util.Collection; + +/** + * Represents a node in a term graph. The graph may contain cycles. A node in a term + * graph can be of three kinds: a variable, a function (possibly constant) and a reference. + * A reference must point to either a function term or a variable. + * + * A term must implement {@link java.lang.Comparable}, but this is only really used for + * comparing the variables. + * + * @author Fedor Isakov + */ +public interface Term extends Comparable { + + /** + * A symbol corresponding to the function/constant name or the variable name. + * TODO: what is the meaning of symbol in case of a reference term? + * NOTE: two variable terms are treated as the same term if their symbols match. + */ + Object symbol(); + + /** + * Arguments of a function term. + */ + Collection arguments(); + + /** + * For a reference term, returns the term this term is referring to. + * For function and variable terms return this term. + */ + Term get(); + + /** + * The kind of this term: function, variable or reference. + */ + boolean is(Kind kind); + + enum Kind { + FUN, + VAR, + REF + } +} diff --git a/reactor/Core/src/jetbrains/mps/unification/TermGraphUnifier.kt b/reactor/Core/src/jetbrains/mps/unification/TermGraphUnifier.kt new file mode 100644 index 00000000..dfc80812 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/unification/TermGraphUnifier.kt @@ -0,0 +1,304 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification + +import gnu.trove.TIntHashSet +import gnu.trove.list.array.TIntArrayList +import gnu.trove.map.hash.TIntObjectHashMap +import jetbrains.mps.unification.Substitution.FailureCause.CYCLE_DETECTED +import jetbrains.mps.unification.Substitution.FailureCause.SYMBOL_CLASH +import jetbrains.mps.unification.Term.Kind.* +import jetbrains.mps.unification.Unification.SuccessfulSubstitution +import jetbrains.mps.unification.Unification.failedSubstitution +import java.util.* + + +/** + * This is an implementation of the "near linear" algorithm for solving syntactic unification + * as described in the paper linked below and also in the textbook of the same author.1 2 + * + * No recursive terms are allowed as a solution, meaning the "occurs check" for variables + * is performed on the input. Variables can also be passed by reference without altering the + * intuitive behaviour of the algorithm. + * + * The "REF" terms are an extension of the term algebra used in [1]. For the purposes of this algorithm, the "REF" + * terms are treated as pre-bound variables. + * + * If successful, the returned {@link Substitution} contains + * the variable bindings. + *

+ * The variables are sorted using {@link java.lang.Comparable} to ensure uniqueness of bindings + * whereas the substituted term is also a variable. + *

+ *

+ * 1. Baader, Franz, and Wayne Snyder. "Unification Theory." Handbook of automated reasoning 1 (2001): 445-532. + * 2. Baader, Franz, and Tobias Nipkow. Term rewriting and all that. Cambridge University Press, 1999. + *
+ * + * @author Fedor Isakov + */ + +class TermGraphUnifier { + + companion object { + val EMPTY =TIntArrayList.wrap(kotlin.IntArray(0)) + } + + private var wrapper: TermWrapper + + private var failureCause: Substitution.FailureCause = Substitution.FailureCause.UKNOWN + private var failureDetails = emptyArray() + + private val backref = IdentityHashMap() + private val origin = ArrayList() + private val innerClass = TIntArrayList() + private val innerSchema = TIntArrayList() + private val innerSize = TIntArrayList() + private val innerVars = TIntObjectHashMap() + private val innerAcyclic = TIntHashSet() + private val innerVisited = TIntHashSet() + + constructor() { + this.wrapper = TermWrapper.ID + } + + constructor(wrapper: TermWrapper) { + this.wrapper = wrapper + } + + fun unify(a: Term, b: Term): Substitution { + return if (unifClosure(toInner(a), toInner(b))) { + findSolution(toInner(a)) + + } else { + failedSubstitution(failureCause, *failureDetails) + } + } + + private fun findSolution(s: Int): Substitution { + return findSolution(s, Unification.EMPTY_SUBSTITUTION) + } + + private fun findSolution(s: Int, defSubs: Substitution) : Substitution { + val z = innerSchema[find(s)] + + if (innerAcyclic.contains(z)) { return defSubs } // not part of a cycle + if (innerVisited.contains(z)) { return failedSubstitution(CYCLE_DETECTED) } // there exists a cycle + + var subs = defSubs + if (origin[z].`is`(FUN)) { + innerVisited.add(z) + + for (c in origin[z].arguments()) { + subs = findSolution(toInner(c), subs) + if (!subs.isSuccessful) break + } + + innerVisited.add(z) + } + + if (subs.isSuccessful) { + innerAcyclic.add(z) + + // avoid unnecessary instatiation + val success = if (subs is SuccessfulSubstitution) subs else SuccessfulSubstitution(subs) + + val vars = innerVars[find(z)] + if (vars != null) { + for (v in vars) { + if (v != z) { + // Keep the order of variables within a binding + if (origin[z].`is`(VAR) && origin[z].compareTo(origin[v]) < 0) { + success.addBinding(fromInner(z), fromInner(v)) + + } else { + success.addBinding(fromInner(v), fromInner(z)) + } + } + } + } + + subs = success + } + + return subs + } + + + private fun unifClosure(a: Int, b: Int): Boolean { + var s = find(a) + var t = find(b) + + if (s == t) { return true } + + var zs = innerSchema[s] + var zt = innerSchema[t] + + while (origin[zs].`is`(REF)) { + s = union(s, getRef(zs)) + zs = innerSchema[s] + } + + while (origin[zt].`is`(REF)) { + t = union(t, getRef(zt)) + zt = innerSchema[t] + } + + // a VAR always matches another term + if (origin[zs].`is`(VAR) || origin[zt].`is`(VAR)) { + union(s, t) + return true + } + + if (origin[zs].`is`(FUN) && origin[zt].`is`(FUN)) { + if (!origin[zs].symbol().eq(origin[zt].symbol())) { + this.failureCause = SYMBOL_CLASH + this.failureDetails = arrayOf(origin[zs].symbol(), origin[zt].symbol()) + return false + } + // union REF terms only if they are the same term + if (origin[s].`is`(REF) == origin[t].`is`(REF)) { + union(s, t) + } + + val zsit = origin[zs].arguments().iterator() + val ztit = origin[zt].arguments().iterator() + while (zsit.hasNext() && ztit.hasNext()) { + if (!unifClosure(toInner(zsit.next()), toInner(ztit.next()))) { + return false // arguments mismatch + } + } + + // fail if different arguments count + return zsit.hasNext() == ztit.hasNext() + + } else { + // something's wrong with the input + throw IllegalStateException("invalid input") + } + } + + + private fun union(a: Int, b: Int): Int { + var s = find(a) + var t = find(b) + + if (s == t) { return s } + + val ssize = innerSize[s] + val tsize = innerSize[t] + + // keep the order: the smaller class gets inserted under the bigger one + if (ssize < tsize) { + val tmp = t + t = s + s = tmp + + } else if (ssize == tsize && origin[s].`is`(VAR) && origin[t].`is`(VAR)) { + // ensure proper order of variables in the substitution + if (origin[t].compareTo(origin[s]) < 0) { + val tmp = t + t = s + s = tmp + } + } + + innerSize[s] = ssize + tsize + prependVars(s, innerVars[t]) + innerClass[t] = s + + // copy the schema + val zs = innerSchema[s] + val zt = innerSchema[t] + if (origin[zs].`is`(REF)) { + innerSchema[s] = zt + + } else if (origin[zs].`is`(VAR) && !(origin[zt].`is`(VAR))) { + innerSchema[s] = zt + } + + return s + } + + private fun find(t: Int): Int { + var repr = innerClass[t] + if (repr == t) { return repr } + + if (repr != innerClass[repr]) { + // find representative and compress paths + val path = TIntArrayList() + path.add(t) + while (repr != innerClass[repr]) { + path.add(repr) + repr = innerClass[repr] + } + for (p in path) { + innerClass[p] = repr + } + } + + return repr + } + + + private fun prependVars(t: Int, vars: TIntArrayList?) { + if (vars?.isEmpty ?: true) { return } + + val newVars = TIntArrayList(innerVars[t] ?: EMPTY) + newVars.addAll(vars) + innerVars.put (t, newVars) + } + + private fun getRef(zs: Int): Int { + return if (origin[zs].`is`(REF)) toInner(origin[zs].get()) else zs + } + + private fun toInner(term: Term): Int { + // Variables with matching symbols are all treated as a single term. + val key = if (term.`is`(VAR)) idSymbol(term.symbol()) else term + return if (backref.containsKey(key)) { + backref[key] !! + + } else { + val wrapped = wrapper.wrap(term) + val next = origin.size + origin.add(wrapped) + innerSchema.add(next) + innerClass.add(next) + innerSize.add(1) + if (wrapped.`is`(VAR)) { + innerVars.put(next, TIntArrayList(intArrayOf(next))) + } + backref.put(key, next) + next + } + } + + private fun fromInner(t: Int): Term { + return wrapper.unwrap(origin[t]) + } + + private fun idSymbol(symbol: Any): Any { + return if (symbol is String) symbol.intern() else symbol + } + + private fun Any?.eq (that: Any?): Boolean { + return if (this == null) that == null else this.equals(that) + } + + +} \ No newline at end of file diff --git a/reactor/Core/src/jetbrains/mps/unification/TermWrapper.java b/reactor/Core/src/jetbrains/mps/unification/TermWrapper.java new file mode 100644 index 00000000..035795a5 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/unification/TermWrapper.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification; + +/** + * Used by the unifier to wrap original terms in order to alter the unification behaviour. + * + * For example, one might want to represent a (term) variable as a constant in order to avoid unwanted matches. + * + * @author Fedor Isakov + */ +public interface TermWrapper { + + Term wrap(Term orig); + + Term unwrap(Term wrapper); + + TermWrapper ID = new TermWrapper() { + + @Override + public Term wrap(Term orig) { + return orig; + } + + @Override + public Term unwrap(Term wrapper) { + return wrapper; + } + + }; + +} diff --git a/reactor/Core/src/jetbrains/mps/unification/Unification.kt b/reactor/Core/src/jetbrains/mps/unification/Unification.kt new file mode 100644 index 00000000..3efb4f11 --- /dev/null +++ b/reactor/Core/src/jetbrains/mps/unification/Unification.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2014-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification + +import jetbrains.mps.unification.Substitution.FailureCause + +import java.util.* + +/** + * This is the main entry point to the unification solver. Presently @{link TermGraphUnifier} is used + * for finding the solution. + * + * @author Fedor Isakov + */ +object Unification { + + val EMPTY_SUBSTITUTION: Substitution = object : Substitution(true) { + override fun toString(): String { + return "[]" + } + } + + val FAILED_SUBSTITUTION: Substitution = object : Substitution(FailureCause.UKNOWN) { + override fun toString(): String { + return "[FAILED_SUBSTITUTION]" + } + } + + fun unify(a: Term, b: Term): Substitution { + val dagUnifier = TermGraphUnifier() + + return dagUnifier.unify(a, b) + } + + fun unify(a: Term, b: Term, wrapper: TermWrapper): Substitution { + val dagUnifier = TermGraphUnifier(wrapper) + + return dagUnifier.unify(a, b) + } + + fun failedSubstitution(failCause: FailureCause): Substitution { + return Substitution(failCause) + } + + fun failedSubstitution(failCause: FailureCause, vararg details: Any?): Substitution { + return Substitution(failCause, *details) + } + + class SuccessfulSubstitution(substitution: Substitution) : Substitution(true) { + + private val myBindings: LinkedList + + init { + this.myBindings = LinkedList(substitution.bindings()) + } + + override fun bindings(): Collection { + return Collections.unmodifiableCollection(myBindings) + } + + override fun toString(): String { + val sb = StringBuilder("[") + var sep = "" + for (b in myBindings) { + sb.append(sep) + sep = ", " + sb.append(b.`var`()).append(" -> ").append(b.term()) + } + return sb.append("]").toString() + } + + fun addBinding(v: Term, n: Term) { + myBindings.addFirst(Substitution.Binding(v, n)) + } + + } +} diff --git a/reactor/README.md b/reactor/README.md new file mode 100644 index 00000000..718541e6 --- /dev/null +++ b/reactor/README.md @@ -0,0 +1,34 @@ +# Constraints Reactor + +An engine for evaluating constraint rules. + +Written in Kotlin and Java. + +[![Build Status](https://travis-ci.org/fisakov/conreactor.svg?branch=master)](https://travis-ci.org/fisakov/conreactor) + +This is an implementation of the [Constraint Handling Rules](http://www.informatik.uni-ulm.de/pm/fileadmin/pm/home/fruehwirth/constraint-handling-rules-book.html) semantics with custom extensions, influenced by [JCHR](https://dtai.cs.kuleuven.be/CHR/JCHR/). + +The main features are built-in support for **terms** as the data structure, including terms **unification**, and **logical variables**. + +The use of terms as constraint parameters enables to implement optimizations for handling multi-head rules. Also, this implementation relies heavily on term unification when matching rules. + +Logical variables enable to pass parameters to constraints as indirect references, which may be assigned later. Assigning a value to an unbound logical variable reactivates the constraint in which it serves as a parameter. Two logical variables may be joined to be treated as one. + +NOTE: this implementation does not include support for parsing the CHR syntax. + + +## License + +Copyright 2014-2017 JetBrains s.r.o. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/reactor/Reactor.iml b/reactor/Reactor.iml new file mode 100644 index 00000000..b5309b5b --- /dev/null +++ b/reactor/Reactor.iml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/Test/Test.iml b/reactor/Test/Test.iml new file mode 100644 index 00000000..ef4f74cd --- /dev/null +++ b/reactor/Test/Test.iml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/reactor/Test/pom.xml b/reactor/Test/pom.xml new file mode 100644 index 00000000..6c20bc4e --- /dev/null +++ b/reactor/Test/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + + jetbrains.mps.logic.conreactor + conreactor-main + 0.9 + .. + + + tests + jar + + + + jetbrains.mps.logic.conreactor + conreactor-api + ${project.parent.version} + + + jetbrains.mps.logic.conreactor + conreactor-core + ${project.parent.version} + + + junit + junit + 4.12 + test + + + + + ${basedir}/src/ + ${basedir}/test/ + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + ${basedir}/src + + + + + test-compile + test-compile + + test-compile + + + + ${basedir}/test + + + + + + + + + diff --git a/reactor/Test/src/jetbrains/mps/unification/test/MockTerm.java b/reactor/Test/src/jetbrains/mps/unification/test/MockTerm.java new file mode 100644 index 00000000..76ce911c --- /dev/null +++ b/reactor/Test/src/jetbrains/mps/unification/test/MockTerm.java @@ -0,0 +1,209 @@ +/* + * Copyright 2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification.test; + +import jetbrains.mps.unification.Term; + +import java.util.*; + +/** + * Created by fyodor on 09.06.2014. + */ +public abstract class MockTerm implements Term { + + public MockTerm() { + } + + public static Term term(Object sym, Term... arguments) { + return new MockFun(sym, arguments); + } + + public static Term var(String name) { + return new MockVar(name); + } + + public static Term ref(Term term) { + return new MockRef(term); + } + + public static Term ref(TermLookup termLookup) { + return new MockRef(termLookup); + } + + interface TermLookup { + Term lookupTerm(); + } + + @Override + public Object symbol() { + return null; + } + + @Override + public Collection arguments() { + return Collections.emptyList(); + } + + @Override + public Term get() { + return this; + } + + @Override + public int compareTo(Term term) { + return String.valueOf(symbol()).compareTo(String.valueOf(term.symbol())); + } + + public static class MockFun extends MockTerm { + private List myArgs; + private Object mySymbol; + + public MockFun(Object symbol, Term... arguments) { + mySymbol = symbol; + this.myArgs = Arrays.asList(arguments); + } + + @Override + public Object symbol() { + return mySymbol; + } + + @Override + public Collection arguments() { + return Collections.unmodifiableList(myArgs); + } + + @Override + public boolean is(Kind kind) { + return Kind.FUN == kind; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(String.valueOf(mySymbol)); + if (!myArgs.isEmpty()) { + sb.append("{"); + String sep = ""; + for (Term arg : myArgs) { + sb.append(sep); sep = " "; + sb.append(arg.toString()); + } + sb.append("}"); + } + return sb.toString(); + } + + @Override + public int hashCode() { + int hash = 43 + mySymbol.hashCode(); + return hash*19 + myArgs.hashCode(); + } + + @Override + public boolean equals(Object o) { + return ((MockFun)o).mySymbol.equals(mySymbol) && + ((MockFun)o).myArgs.equals(myArgs); + } + } + + public static class MockVar extends MockTerm { + private String myName; + + public MockVar(String name) { + myName = name; + } + + @Override + public Object symbol() { + return myName; + } + + @Override + public boolean is(Kind kind) { + return Kind.VAR == kind; + } + + @Override + public String toString() { + return String.valueOf(myName); + } + + @Override + public int hashCode() { + return 43 + myName.hashCode(); + } + + @Override + public boolean equals(Object o) { + return ((MockVar)o).myName.equals(myName); + } + + } + + public static class MockRef extends MockTerm { + + private Term term; + private TermLookup termLookup; + + public MockRef(Term term) { + this.term = term; + } + + public MockRef(TermLookup termLookup) { + this.termLookup = termLookup; + } + + @Override + public Object symbol() { + return "^"; + } + + @Override + public final Term get() { + if (term == null && termLookup != null) { + term = termLookup.lookupTerm(); + termLookup = null; + } + return term; + } + + @Override + public boolean is(Kind kind) { + return Kind.REF == kind; + } + + @Override + public String toString() { + Term t = get(); + return t != null ? "^"+ t.symbol() : "^"; + } + + @Override + public boolean equals(Object that) { + if (that == this) return true; + if (that == null || getClass() != that.getClass()) return false; + + return get() == ((MockTerm) that).get(); + } + + @Override + public int hashCode() { + Term t = get(); + return t != null ? System.identityHashCode(t) : 0; + } + } +} diff --git a/reactor/Test/src/jetbrains/mps/unification/test/MockTermsParser.java b/reactor/Test/src/jetbrains/mps/unification/test/MockTermsParser.java new file mode 100644 index 00000000..d0bd061e --- /dev/null +++ b/reactor/Test/src/jetbrains/mps/unification/test/MockTermsParser.java @@ -0,0 +1,297 @@ +/* + * Copyright 2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification.test; + +import jetbrains.mps.unification.Term; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static jetbrains.mps.unification.test.MockTerm.*; + +/** + * Created by fyodor on 10.06.2014. + */ +public class MockTermsParser { + + public static Collection parseAll(String str) { + return new RecursiveDescent().parse(str); + } + + public static Term parse(String str) { + List terms = new RecursiveDescent().parse(str); + if (terms.size() != 1) { + throw new IllegalArgumentException("expected single asTerm or asVar"); + } + return terms.get(0); + } + + public static Term parseTerm(String str) { + return parse(str); + } + + public static Term parseVar(String str) { + return parse(str); + } + + private static class RecursiveDescent { + + private Token lastToken; + private LinkedList termsStack = new LinkedList(); + private LinkedList termLabelsStack = new LinkedList(); + private LinkedList> argumentsStack = new LinkedList>(); + private int lastLabel = -1; + private Map termRefs = new HashMap(); + // initialized on the parse finished + private LookupHelper lookupHelper = new LookupHelper(); + + private List parse(String toParse) { + parseNextToken(Token.START, null); + loop(toParse); + parseNextToken(Token.END, null); + checkFinalState(); + checkAllRefsExist(); + lookupHelper.setTermRefs(Collections.unmodifiableMap(new HashMap(termRefs))); + return Collections.unmodifiableList(argumentsStack.pop()); + } + + private void loop(String toParse) { + Matcher matcher = null; + do { + for (Token token: Token.values()) { + if (token.pattern == null) continue; + + if ((matcher = token.pattern.matcher(toParse)).lookingAt()) { + parseNextToken(token, matcher.group()); + toParse = toParse.substring(matcher.end()); + break; + } + } + // see if the last matching succeeded + assert matcher != null; + if (!matcher.lookingAt() && !matcher.hitEnd()) { + throw new ParseException("unexpected input: '"+toParse+"'"); + } + } while (!matcher.hitEnd()); + } + + private void parseNextToken(Token token, String value) { + switch (token) { + case START: + argumentsStack.push(new ArrayList()); + break; + case END: + checkLastTokenOneOf(Token.TERM, Token.VAR, Token.VARREF, Token.RBRACE); + if (lastToken == Token.TERM) { + emptyTerm(); + } + break; + case TERM: + if (lastToken == Token.TERM) { + emptyTerm(); + } + beginTerm(value); + break; + case VAR: + checkLastTokenNotOneOf(Token.LABEL); + if (lastToken == Token.TERM) { + emptyTerm(); + } + addVar(value); + break; + case VARREF: + checkLastTokenNotOneOf(Token.LABEL); + if (lastToken == Token.TERM) { + emptyTerm(); + } + addVarRef(value.substring(1)); + break; + case LBRACE: + checkLastTokenOneOf(Token.TERM); + beginChildren(); + break; + case RBRACE: + checkLastTokenOneOf(Token.TERM, Token.VAR, Token.VARREF, Token.REF, Token.RBRACE); + if (lastToken == Token.TERM) { + emptyTerm(); + } + checkCloseBraceState(); + endChildren(); + break; + case WHITESPACE: + return; // ignore + case LABEL: + checkLastTokenNotOneOf(Token.LABEL); + if (lastToken == Token.TERM) { + emptyTerm(); + } + lastLabel = Integer.parseInt(value.substring(1)); + break; + case REF: + checkLastTokenNotOneOf(Token.LABEL); + if (lastToken == Token.TERM) { + emptyTerm(); + } + addRef(value); + break; + } + this.lastToken = token; + } + + private void checkLastTokenOneOf(Token ... tokens) { + if (lastToken == Token.WHITESPACE) return; + for (Token token : tokens) { + if (token == lastToken) return; + } + throw new ParseException("parse error: unexpected token '"+lastToken+"'"); + } + + private void checkLastTokenNotOneOf(Token ... tokens) { + if (lastToken == Token.WHITESPACE) return; + for (Token token : tokens) { + if (token == lastToken) { + throw new ParseException("parse error: unexpected token '"+lastToken+"'"); + } + } + } + + private void checkFinalState() { + if (argumentsStack.size() != 1 || argumentsStack.peek().isEmpty() || !termsStack.isEmpty()) { + throw new ParseException("unexpected end of input"); + } + } + + private void checkCloseBraceState() { + if (argumentsStack.isEmpty() || argumentsStack.peek().isEmpty() || termsStack.isEmpty()) { + throw new ParseException("unexpected closing brace"); + } + } + + private void checkAllRefsExist() { + for (Map.Entry e: termRefs.entrySet()) { + if (e.getValue() == null) { + throw new ParseException("non-existing label '" + e.getKey() + "'"); + } + } + } + + private void beginTerm(String name) { + termsStack.push(name); + termLabelsStack.push(lastLabel >= 0 ? lastLabel : null); + lastLabel = -1; + } + + private void emptyTerm() { + String name = termsStack.pop(); + Integer label = termLabelsStack.pop(); + Term newTerm = MockTerm.term(name); + argumentsStack.peek().add(newTerm); + if (label != null) { + termRefs.put(label, newTerm); + } + } + + private void beginChildren(){ + argumentsStack.push(new ArrayList()); + } + + private void endChildren() { + List arguments = argumentsStack.pop(); + String name = termsStack.pop(); + Integer label = termLabelsStack.pop(); + Term newTerm = MockTerm.term(name, arguments.toArray(new Term[arguments.size()])); + argumentsStack.peek().add(newTerm); + if (label != null) { + termRefs.put(label, newTerm); + } + } + + private void addVar(String name) { + argumentsStack.peek().add(MockTerm.var(name)); + } + + private void addVarRef(String name) { + argumentsStack.peek().add(MockTerm.ref(MockTerm.var(name))); + } + + private void addRef(String ref) { + final int label = Integer.parseInt(ref.substring(1)); + if (termRefs.containsKey(label) && termRefs.get(label) != null) { + argumentsStack.peek().add(MockTerm.ref(termRefs.get(label))); + } + else { + termRefs.put(label, null); + argumentsStack.peek().add(MockTerm.ref(lookupHelper.lookup(label))); + } + } + } + + private static enum Token { + START(null), + END(null), + TERM(Pattern.compile("[a-z][a-zA-Z0-9_]*")), + VAR(Pattern.compile("[A-Z][a-zA-Z0-9_]*")), + LBRACE(Pattern.compile("\\{")), + RBRACE(Pattern.compile("\\}")), + WHITESPACE(Pattern.compile("\\s+")), + LABEL(Pattern.compile("@[0-9]+")), + REF(Pattern.compile("\\^[0-9]+")), + VARREF(Pattern.compile("\\^[A-Z][a-zA-Z0-9_]*")); + + private Pattern pattern; + + Token(Pattern pattern) { + this.pattern = pattern; + } + } + + public static class ParseException extends RuntimeException { + private ParseException(String s) { + super(s); + } + } + + private static class LookupHelper { + private Map termRefs; + + private void setTermRefs (Map termRefs) { + this.termRefs = termRefs; + } + + public TermLookup lookup(final int label) { + return new TermLookup() { + @Override + public Term lookupTerm() { + if (termRefs == null) { + throw new IllegalStateException("call to uninitialized lookup"); + } + if (!termRefs.containsKey(label)) { + throw new IllegalStateException("non-existing label '" + label + "'"); + } + return termRefs.get(label); + } + }; + } + + } +} + + + + + diff --git a/reactor/Test/src/program/MockConstraint.kt b/reactor/Test/src/program/MockConstraint.kt new file mode 100644 index 00000000..94d7a3e3 --- /dev/null +++ b/reactor/Test/src/program/MockConstraint.kt @@ -0,0 +1,30 @@ +package program + +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.program.ConstraintSymbol +import jetbrains.mps.logic.reactor.program.Predicate +import java.util.* + +/** + * @author Fedor Isakov + */ + +data class MockConstraint(val symbol: ConstraintSymbol, val arguments: List) : Constraint { + + constructor(symbol: ConstraintSymbol, vararg args: Any) : this(symbol, listOf(* args)) {} + + override fun arguments(): List = arguments + + override fun patternPredicates(args: Collection<*>): Collection = emptyList() + + override fun symbol(): ConstraintSymbol = symbol + + override fun argumentTypes(): List> = arguments.map { arg -> arg.javaClass } + + override fun toString(): String = "${symbol()}(${arguments().joinToString()})" + +} + diff --git a/reactor/Test/src/program/MockProgram.kt b/reactor/Test/src/program/MockProgram.kt new file mode 100644 index 00000000..365e8348 --- /dev/null +++ b/reactor/Test/src/program/MockProgram.kt @@ -0,0 +1,178 @@ +/** + * @author Fedor Isakov + */ + +import jetbrains.mps.logic.reactor.evaluation.SessionSolver +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.program.* +import program.MockConstraint +import java.util.* +import java.util.Collections.* + +class ProgramBuilder(val registry: MockConstraintRegistry) { + + private val handlers = ArrayList() + + fun addHandler(handler: Handler) { + for (r in handler.rules()) { + registry.update(r) + } + handlers.add(handler) + } + + fun constraint(symbol: ConstraintSymbol, vararg args: Any): Constraint = MockConstraint(symbol, listOf(* args)) + + fun program(name: String): Program = MockProgram(name, handlers, registry) + +} + +open class HandlerBuilder(val name: String, val primary: Iterable) { + val rules = ArrayList() + + fun appendRule(rule: Rule) { + rules.add(rule) + } + + fun toHandler(): Handler = MockHandler(name, primary, rules) +} + +open class RuleBuilder(val tag: String) { + val kept = ArrayList() + val replaced = ArrayList() + val guard = ArrayList() + val body = ArrayList>() + + fun appendHeadKept(vararg cst: Constraint) { + kept.addAll(cst) + } + fun appendHeadReplaced(vararg cst: Constraint) { + replaced.addAll(cst) + } + fun appendGuard(vararg prd: Predicate) { + guard.addAll(prd) + } + fun appendBody(alt: Boolean, vararg andItem: AndItem) { + if (alt || body.isEmpty()) body.add(ArrayList()) + body.last().addAll(andItem) + } + fun toRule(): Rule = MockRule(tag, kept, replaced, guard, body) +} + +class MockHandler( + val name: String, + val primary: Iterable, + val rules: List) : Handler() { + + override fun name(): String = name + + override fun primarySymbols(): Iterable = primary + + override fun rules(): Iterable = rules +} + +class MockRule( + val tag: String, + val kept: Collection, + val replaced: Collection, + val guard: Collection, + val body: Collection>) : Rule() { + + override fun kind(): Kind = TODO() + + override fun tag(): String = tag + + override fun headKept(): Iterable = kept + + override fun headReplaced(): Iterable = replaced + + override fun guard(): Iterable = guard + + override fun body(): Iterable = body.first() + + override fun bodyAlternation(): Iterable> = body + + override fun all(): Iterable = + if (body.isEmpty()) throw InvalidRuleException("no body") + else (kept + replaced).map { it as AndItem } + guard + body.flatten() +} + +class MockProgram(val name: String, val handlers: List, val registry: MockConstraintRegistry) : Program() { + + override fun name(): String = name + + override fun constraintSymbols(): Iterable = + registry.constraintSymbols() + + override fun constraintArgumentTypes(constraintSymbol: ConstraintSymbol): List> = + registry.constraintArgTypes(constraintSymbol) + + override fun predicateSymbols(): Iterable = + registry.predicateSymbols() + + override fun instantiateArguments(arguments: List<*>, logicalContext: LogicalContext): List<*> = + arguments.map { a -> + if (a is MetaLogical<*>) logicalContext.variable(a) + else a + } + + override fun handlers(): Iterable = unmodifiableCollection(handlers) +} + + +class MockConstraintRegistry(val sessionSolver: SessionSolver) { + + private val myConstraintArgTypes = HashMap>>().withDefault { Collections.emptyList() } + + private val myPredicateSymbols = HashSet() + + fun update(rule: Rule) { + for(item in rule.all()) { + checkValid(item) + introduce(item) + } + } + + fun checkValid(item: AndItem) { + if (item.symbol().arity() != item.arguments().size) { + throw InvalidConstraintException("arity mismatch: ${item.symbol()}") + } + for (arg in item.arguments()) { + if (arg == null) { + throw InvalidConstraintException("argument is null: ${item.symbol()}") + } + } + when(item) { + is Constraint -> { + if (myConstraintArgTypes.containsKey(item.symbol())) { + if (!item.argumentTypes().equals(myConstraintArgTypes.get(item.symbol()))) { + throw InvalidConstraintException("argument types mismatch: ${item.symbol()}") + } + } + } + is Predicate -> { + // nothing + } + else -> throw InvalidConstraintException("unknown item: ${item}") + } + } + + fun introduce(item: AndItem) { + when(item) { + is Constraint -> myConstraintArgTypes.put(item.symbol(), item.argumentTypes()) + is Predicate -> myPredicateSymbols.add(item.symbol()) + else -> throw InvalidConstraintException("unknown item: ${item}") + } + } + + fun constraintSymbols(): Iterable = + unmodifiableSet(myConstraintArgTypes.keys) + + fun constraintArgTypes(symbol: ConstraintSymbol): List> = + unmodifiableList(myConstraintArgTypes.getOrPut(symbol, { emptyList() })) + + fun predicateSymbols(): Iterable = + unmodifiableSet(myPredicateSymbols) + +} + diff --git a/reactor/Test/src/solver/EqualsSolver.kt b/reactor/Test/src/solver/EqualsSolver.kt new file mode 100644 index 00000000..58a0243d --- /dev/null +++ b/reactor/Test/src/solver/EqualsSolver.kt @@ -0,0 +1,128 @@ +package solver + +import jetbrains.mps.logic.reactor.evaluation.AbstractSolver +import jetbrains.mps.logic.reactor.evaluation.EvaluationFailureException +import jetbrains.mps.logic.reactor.evaluation.EvaluationSession +import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.logical.JoinableLogical +import jetbrains.mps.logic.reactor.program.Predicate +import jetbrains.mps.logic.reactor.program.PredicateSymbol + +class EqualsSolver : AbstractSolver() { + + companion object { + fun eq(left: Any, right: Any): TestEqPredicate = TestEqPredicate(left, right) + } + + override fun ask(invocation: PredicateInvocation): Boolean { + return _ask(invocation.arguments().get(0), invocation.arguments().get(1)) + } + + private fun _ask(left: Any?, right: Any?): Boolean { + return if (left is JoinableLogical<*> && right is JoinableLogical<*>) { + ask_logical_logical(left, right) + } else if (left is JoinableLogical<*>) { + ask_logical_value(left, right) + } else if (right is JoinableLogical<*>) { + ask_value_logical(left, right) + } else { + ask_value_value(left, right) + } + } + + override fun tell(invocation: PredicateInvocation) { + _tell(invocation.arguments().get(0), invocation.arguments().get(1)) + } + + private fun _tell(left: Any?, right: Any?) { + if (left is JoinableLogical<*> && right is JoinableLogical<*>) { + tell_logical_logical(left as JoinableLogical, right as JoinableLogical) + } else if (left is JoinableLogical<*>) { + tell_logical_value(left as JoinableLogical, right) + } else if (right is JoinableLogical<*>) { + tell_value_logical(left, right as JoinableLogical) + } else { + tell_value_value(left, right) + } + } + + fun ask_logical_logical(left: JoinableLogical<*>, right: JoinableLogical<*>): Boolean { + if (left.findRoot() == right.findRoot()) return true + return left.isBound && right.isBound && left.findRoot().value() == right.findRoot().value() + } + + fun ask_logical_value(left: JoinableLogical<*>, right: Any?): Boolean { + return left.isBound && left.findRoot().value() == right + } + + fun ask_value_logical(left: Any?, right: JoinableLogical<*>): Boolean { + return right.isBound && right.findRoot().value() == left + } + + fun ask_value_value(left: Any?, right: Any?): Boolean { + return left == right + } + + fun tell_logical_logical(left: JoinableLogical, right: JoinableLogical) { + if (left == right) return + + val leftRepr = left.findRoot() + val rightRepr = right.findRoot() + + if (leftRepr == rightRepr) return + + leftRepr.union(rightRepr, { a, b -> tell_value_value(a, b)}) + } + + fun tell_logical_value(left: JoinableLogical, right: T?) { + if (left.isBound) { + check(left.findRoot().value() == right) + } + else { + left.findRoot().setValue(right) + } + } + + fun tell_value_logical(left: T?, right: JoinableLogical) { + if (right.isBound) { + check(right.findRoot().value() == left) + } + else { + right.findRoot().setValue(left) + } + } + + fun tell_value_value(left: Any?, right: Any?) { + check(left == right) + } + + private fun check(condition: Boolean) { + if (!condition) throw EvaluationFailureException("condition is not satisfied") + } + +} + +infix fun T.is_eq(value: T): Boolean = EvaluationSession.current().let { session -> + session.sessionSolver().ask(session.invocation(EqualsSolver.eq(this, value), mockLogicalContext())) +} + +infix fun T.eq(value: T) = EvaluationSession.current().let { session -> + session.sessionSolver().tell(session.invocation(EqualsSolver.eq(this, value), mockLogicalContext())) +} + +private fun mockLogicalContext(): LogicalContext { + return object : LogicalContext { + override fun variable(metaLogical: MetaLogical?): Logical = TODO() + } +} + +data class TestEqPredicate(val left: Any, val right: Any) : Predicate { + + override fun arguments(): List = listOf(left, right) + + override fun symbol(): PredicateSymbol = PredicateSymbol("equals", 2) + +} \ No newline at end of file diff --git a/reactor/Test/src/solver/MockSessionSolver.kt b/reactor/Test/src/solver/MockSessionSolver.kt new file mode 100644 index 00000000..3119c4e5 --- /dev/null +++ b/reactor/Test/src/solver/MockSessionSolver.kt @@ -0,0 +1,23 @@ +package solver + +import jetbrains.mps.logic.reactor.evaluation.AbstractSolver +import jetbrains.mps.logic.reactor.evaluation.EvaluationTrace +import jetbrains.mps.logic.reactor.evaluation.SessionSolver +import jetbrains.mps.logic.reactor.program.JavaPredicateSymbol +import jetbrains.mps.logic.reactor.program.PredicateSymbol + +/** + * @author Fedor Isakov + */ + +open class MockSessionSolver(val expressionSolver: AbstractSolver, val equalsSolver: AbstractSolver) : SessionSolver() { + + override fun registerSymbol(predicateSymbol: PredicateSymbol, computingTracer: EvaluationTrace?) { + when (predicateSymbol) { + is JavaPredicateSymbol -> registerSolver(predicateSymbol, expressionSolver) + PredicateSymbol("equals", 2) -> registerSolver(predicateSymbol, equalsSolver) + else -> throw UnsupportedOperationException("not implemented") + } + } + +} diff --git a/reactor/Test/test/JavaExpressionHelper.kt b/reactor/Test/test/JavaExpressionHelper.kt new file mode 100644 index 00000000..5e7dcad3 --- /dev/null +++ b/reactor/Test/test/JavaExpressionHelper.kt @@ -0,0 +1,147 @@ +import jetbrains.mps.logic.reactor.evaluation.AbstractSolver +import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.MetaLogical +import jetbrains.mps.logic.reactor.program.AndItem +import jetbrains.mps.logic.reactor.program.JavaPredicateSymbol +import jetbrains.mps.logic.reactor.program.Predicate +import jetbrains.mps.logic.reactor.program.PredicateSymbol +import java.util.* + +/** + * @author Fedor Isakov + */ + + +fun expression(body: () -> Boolean): ConjBuilder.() -> Unit = { + add(JavaPredicateSymbol.withArity(0).withCode(body)) +} + +fun , LPX: MetaLogical> + expression(body: (LX) -> Boolean, x: LPX): ConjBuilder.() -> Unit = { + add(JavaPredicateSymbol.withArity(1).withCode(body, x)) + } + +fun , LPX: MetaLogical, + Y, LY: Logical, LPY: MetaLogical> + expression(body: (LX, LY) -> Boolean, x: LPX, y: LPY): ConjBuilder.() -> Unit = { + add(JavaPredicateSymbol.withArity(2).withCode(body, x, y)) + } + +fun , LPX: MetaLogical, + Y, LY: Logical, LPY: MetaLogical, + Z, LZ: Logical, LPZ: MetaLogical> + expression(body: (LX, LY, LZ) -> Boolean, x: LPX, y: LPY, z: LPZ): ConjBuilder.() -> Unit = { + add(JavaPredicateSymbol.withArity(3).withCode(body, x, y, z)) + } + +fun statement(body: () -> Unit): ConjBuilder.() -> Unit = { + add(JavaPredicateSymbol.withArity(0).withCode { body.invoke(); true }) +} + +fun > + statement( + body: (Logical) -> Unit, x: LPX): ConjBuilder.() -> Unit = { + add(JavaPredicateSymbol.withArity(1).withCode({ x -> body.invoke(x); true }, x)) + } + +fun , + Y, LPY: MetaLogical> + statement(body: (Logical, Logical) -> Unit, x: LPX, y: LPY): ConjBuilder.() -> Unit = { + add(JavaPredicateSymbol.withArity(2).withCode({ x, y -> body.invoke(x, y); true }, x, y)) + } + +fun , + Y, LPY: MetaLogical, + Z, LPZ: MetaLogical> + statement(body: (Logical, Logical, Logical) -> Unit, x: LPX, y: LPY, z: LPZ): ConjBuilder.() -> Unit = { + add(JavaPredicateSymbol.withArity(3).withCode({ x, y, z -> body.invoke(x, y, z); true }, x, y, z)) + } + +class ExpressionSolver : AbstractSolver() { + + override fun ask(invocation: PredicateInvocation): Boolean { + return javaPredicates[invocation.arguments().get(0)]?.expr?.invoke(invocation.arguments().drop(1)) ?: + ERROR("no such symbol ${invocation.predicate().symbol()}") + } + + override fun tell(invocation: PredicateInvocation) { + when (invocation.predicate().symbol()) { + is JavaPredicateSymbol -> javaPredicates[invocation.arguments().get(0)]?.expr?.invoke(invocation.arguments().drop(1)) + else -> ERROR("uknown symbol ${invocation.predicate().symbol()}") + } + } + + fun addMaybeJavaPredicate(item: AndItem) { + if (item is TestJavaPredicate) { + addJavaPredicate(item) + } + } + + private val javaPredicates = HashMap() + + private fun addJavaPredicate(javaPredicate: TestJavaPredicate) { + javaPredicates[javaPredicate.args[0] as Int] = javaPredicate + } + + private fun ERROR(msg: String) : Nothing = throw IllegalArgumentException(msg) +} + +interface JavaExpression { + fun invoke(args: List<*>): Boolean +} + +data class TestJavaPredicate(val symbol: JavaPredicateSymbol, val expr: JavaExpression, val args: List<*>) : Predicate { + + override fun arguments(): List<*> = args + + override fun symbol(): PredicateSymbol = symbol + +} + +private fun JavaPredicateSymbol.withCode(code: () -> Boolean) = + TestJavaPredicate(this, JavaExpression0(code), listOf(System.identityHashCode(code))) + +private fun , LPX: MetaLogical> + JavaPredicateSymbol.withCode(code: (LX) -> Boolean, x: LPX) = + TestJavaPredicate(this, JavaExpression1(code), listOf(System.identityHashCode(code), x)) + +private fun , LPX: MetaLogical, + Y, LY: Logical, LPY: MetaLogical> + JavaPredicateSymbol.withCode(code: (LX, LY) -> Boolean, x: LPX, y: LPY) = + TestJavaPredicate(this, JavaExpression2(code), listOf(System.identityHashCode(code), x, y)) + +private fun , LPX: MetaLogical, + Y, LY: Logical, LPY: MetaLogical, + Z, LZ: Logical, LPZ: MetaLogical> + JavaPredicateSymbol.withCode(code: (LX, LY, LZ) -> Boolean, x: LPX, y: LPY, z: LPZ) = + TestJavaPredicate(this, JavaExpression3(code), listOf(System.identityHashCode(code), x, y, z)) + +private class JavaExpression0(val code: () -> Boolean) : JavaExpression { + override fun invoke(args: List<*>): Boolean { + if (args.size != 0) throw IllegalArgumentException("arity mismatch") + return code() + } +} + +private class JavaExpression1(val code: (X) -> Boolean) : JavaExpression { + override fun invoke(args: List<*>): Boolean { + if (args.size != 1) throw IllegalArgumentException("arity mismatch") + return code(args[0] as X) + } +} + +private class JavaExpression2(val code: (X, Y) -> Boolean) : JavaExpression { + override fun invoke(args: List<*>): Boolean { + if (args.size != 2) throw IllegalArgumentException("arity mismatch") + return code(args[0] as X, args[1] as Y) + } +} + +private class JavaExpression3(val code: (X, Y, Z) -> Boolean) : JavaExpression { + override fun invoke(args: List<*>): Boolean { + if (args.size != 3) throw IllegalArgumentException("arity mismatch") + return code(args[0] as X, args[1] as Y, args[2] as Z) + } +} + diff --git a/reactor/Test/test/LogicalHelper.kt b/reactor/Test/test/LogicalHelper.kt new file mode 100644 index 00000000..ee6971c8 --- /dev/null +++ b/reactor/Test/test/LogicalHelper.kt @@ -0,0 +1,65 @@ +import jetbrains.mps.logic.reactor.core.LogicalObserver +import jetbrains.mps.logic.reactor.core.LogicalImpl +import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation +import jetbrains.mps.logic.reactor.logical.* +import jetbrains.mps.logic.reactor.program.Predicate +import jetbrains.mps.logic.reactor.program.PredicateSymbol +import java.util.* + +/** + * @author Fedor Isakov + */ + + +fun anon(value: T) = LogicalImpl(value) + +fun logical(name: String) = LogicalImpl(name) + +fun logical(name1: String, name2: String) = Pair(LogicalImpl(name1), LogicalImpl(name2)) + +fun logical(name1: String, name2: String, name3: String) = + Triple(LogicalImpl(name1), LogicalImpl(name2), LogicalImpl(name3)) + +inline fun metaLogical(name: String) = MetaLogical(name, T::class.java) + +inline fun metaLogical(name1: String, name2: String) = + Pair(MetaLogical(name1, T::class.java), MetaLogical(name2, T::class.java)) + +inline fun metaLogical(name1: String, name2: String, name3: String) = + Triple( + MetaLogical(name1, T::class.java), + MetaLogical(name2, T::class.java), + MetaLogical(name3, T::class.java)) + +fun Logical.get(): T = findRoot().value() + +fun Logical.getNullable(): T? = findRoot().value() + +fun Logical.set(t: T) { + if (this is JoinableLogical) + findRoot().setValue(t) + else + throw IllegalStateException("unexpected receiver $this") +} + +data class MockObserverEvent(val logical: Logical<*>, val event: String) {} + +fun value(logical: Logical<*>) = MockObserverEvent(logical, "value") + +fun parent(logical: Logical<*>) = MockObserverEvent(logical, "parent") + +class MockObserver : LogicalObserver { + + val events = ArrayList() + + override fun valueUpdated(logical: Logical<*>) { events.add(value(logical))} + + override fun parentUpdated(logical: Logical<*>) { events.add(parent(logical))} + + fun getAndClearEvents(): Set { + val tmp = ArrayList(events) + events.clear() + return tmp.toSet() + } +} + diff --git a/reactor/Test/test/RulesHelper.kt b/reactor/Test/test/RulesHelper.kt new file mode 100644 index 00000000..39af8f02 --- /dev/null +++ b/reactor/Test/test/RulesHelper.kt @@ -0,0 +1,172 @@ +import jetbrains.mps.logic.reactor.core.StoreItem +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.program.* +import program.MockConstraint +import solver.EqualsSolver +import solver.TestEqPredicate +import java.util.* + +/** + * @author Fedor Isakov + */ + +class Builder(val env: Environment, val handlers: List) { + val rules: List + get() = handlers.flatMap { it.rules() } + +} + +class Environment(val programBuilder: ProgramBuilder? = null) { + val equalsSolver = EqualsSolver() + val expressionSolver = ExpressionSolver() +} + +fun programWithRules(vararg ruleBuilders : Environment.() -> Rule): Builder { + return programWithRules(Environment(), ruleBuilders) +} + +fun programWithRules(pb: ProgramBuilder, vararg ruleBuilders : Environment.() -> Rule): Builder { + return programWithRules(Environment(pb), ruleBuilders) +} + +private fun programWithRules(env: Environment, ruleBuilders: Array Rule>): Builder { + return builder(env, arrayOf(handler("test", emptyList(), * ruleBuilders))) +} + +fun programWithHandlers(vararg handlerBuilders : Environment.() -> Handler): Builder { + return builder(Environment(), handlerBuilders) +} + +private fun builder(env: Environment, handlerBlocks: Array Handler>): Builder { + val handlers = ArrayList() + with (env) { + for (block in handlerBlocks) { + handlers.add(block()) + } + } + return Builder(env, handlers) +} + +fun handler(name: String, primary: Iterable, vararg ruleBlocks: Environment.() -> Rule): Environment.() -> Handler = { + val hb = HandlerBuilder(name, primary) + for (block in ruleBlocks) { + hb.appendRule(this.block()) + } + hb.toHandler() +} + +fun rule(tag: String, vararg component:RB.() -> Unit): Environment.() -> Rule = { + val rb = RB(this, tag) + for (cmp in component) { + rb.cmp() + } + rb.toRule() +} + +fun headKept(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { + appendHeadKept( * buildConjunction(Constraint::class.java, env, content).toArray()) +} + +fun headReplaced(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { + appendHeadReplaced( * buildConjunction(Constraint::class.java, env, content).toArray()) +} + +fun guard(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { + appendGuard( * buildConjunction(Predicate::class.java, env, content).toArray()) +} + +fun body(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { + appendBody(false, * buildConjunction(AndItem::class.java, env, content).toArray()) +} + +fun altBody(vararg content : ConjBuilder.() -> Unit): RB.() -> Unit = { + appendBody(true, * buildConjunction(AndItem::class.java, env, content).toArray()) +} + +fun constraint(id: String, vararg args: Any): ConjBuilder.() -> Unit = { + add(createConstraint(args, id)) +} + + +fun equals(left: Any, right: Any): ConjBuilder.() -> Unit = { + add(TestEqPredicate(left, right)) +} + +fun occurrence(id: String, vararg args: Any) : ConstraintOccurrence = TestConstraintOccurrence(id, * args) + +class RB(val env: Environment, tag: String) : RuleBuilder(tag) { + +} + +class ConjBuilder(val type: Class, val env: Environment) { + val constraints = ArrayList() + + fun createConstraint(args: Array, id: String): Constraint { + return env.programBuilder ?. + constraint(ConstraintSymbol(id, args.size), * args) ?: + MockConstraint(ConstraintSymbol(id, args.size), * args) + } + + fun add(item: AndItem): Unit { + if (!type.isAssignableFrom(item.javaClass)) + throw IllegalArgumentException("unexpected constraint class '${item.javaClass}'") + constraints.add(item) + env.expressionSolver.addMaybeJavaPredicate(item) + } + + @Suppress("UNCHECKED_CAST") + fun toArray(): Array = + if (Constraint::class.java.isAssignableFrom(type)) + Array(constraints.size) { + constraints.get(it) as Constraint + } as Array + else if (Predicate::class.java.isAssignableFrom(type)) + Array(constraints.size) { + constraints.get(it) as Predicate + } as Array + else + Array(constraints.size) { + constraints.get(it) + } as Array +} + +private fun buildConjunction(type: Class, + env: Environment, + content: Array Unit>): ConjBuilder +{ + val conjBuilder = ConjBuilder(type, env) + for (c in content) { + conjBuilder.c() + } + return conjBuilder +} + +data class TestConstraintOccurrence(val constraint: Constraint, val arguments: List, val id: Int) : + ConstraintOccurrence, + StoreItem +{ + override var alive: Boolean = true + + override var stored: Boolean = false + + companion object { + val random = Random() + } + + constructor(id: String, vararg args: Any) : + this(MockConstraint(ConstraintSymbol.symbol(id, args.size)), listOf(* args), random.nextInt()) {} + + override fun constraint(): Constraint = constraint + + override fun arguments(): List = arguments + + override fun logicalContext(): LogicalContext = TODO() + + override fun terminate() { + this.alive = false + } + + override fun toString(): String = "${constraint().symbol()}(${arguments().joinToString()})" + +} \ No newline at end of file diff --git a/reactor/Test/test/TestController.kt b/reactor/Test/test/TestController.kt new file mode 100644 index 00000000..ff0a7802 --- /dev/null +++ b/reactor/Test/test/TestController.kt @@ -0,0 +1,703 @@ +import jetbrains.mps.logic.reactor.core.Controller +import jetbrains.mps.logic.reactor.core.SessionObjects +import jetbrains.mps.logic.reactor.core.invocation +import jetbrains.mps.logic.reactor.core.occurrence +import jetbrains.mps.logic.reactor.evaluation.* +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.logical.LogicalContext +import jetbrains.mps.logic.reactor.program.* +import jetbrains.mps.logic.reactor.util.cons +import org.jetbrains.kotlin.js.parser.parse +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import solver.EqualsSolver +import solver.MockSessionSolver +import solver.eq +import solver.is_eq + +/** + * @author Fedor Isakov + */ + + +class TestController { + + @Before fun beforeTest() { + } + + @After fun afterTest() { + MockSession.deinit() + } + + private class MockSession(val program: Program, val solver: SessionSolver) : EvaluationSession(), SessionObjects { + lateinit var controller: Controller + override fun handler(): Controller = controller + override fun sessionSolver(): SessionSolver = solver + override fun storeView(): StoreView = TODO() + + class MockBackend(val session: MockSession) : Backend { + override fun current(): EvaluationSession = session + override fun createConfig(program: Program): Config = TODO() + } + + override fun invocation(predicate: Predicate, logicalContext: LogicalContext): PredicateInvocation = + predicate.invocation(program.instantiateArguments(predicate.arguments(), logicalContext), logicalContext) + + override fun occurrence(constraint: Constraint, logicalContext: LogicalContext): ConstraintOccurrence = + constraint.occurrence(controller, program.instantiateArguments(constraint.arguments(), logicalContext), logicalContext) + + companion object { + lateinit var ourBackend : MockBackend + + fun init(program: Program, solver: SessionSolver) { + ourBackend = MockBackend(MockSession(program, solver)) + setBackend(ourBackend) + } + + fun deinit() { + clearBackend(ourBackend) + } + } + } + + private fun sessionSolver(expressionSolver: ExpressionSolver, equalsSolver: EqualsSolver): SessionSolver = + MockSessionSolver(expressionSolver, equalsSolver).apply { + init(PredicateSymbol("equals", 2), JavaPredicateSymbol.EXPRESSION0, JavaPredicateSymbol.EXPRESSION1, JavaPredicateSymbol.EXPRESSION2, JavaPredicateSymbol.EXPRESSION3) } + + private fun Builder.controller(vararg occurrences: ConstraintOccurrence): Controller { + val solver = sessionSolver(env.expressionSolver, env.equalsSolver) + val program = MockProgram("test", handlers, registry = MockConstraintRegistry(solver)) + MockSession.init(program, solver) + val controller = Controller(program, storeView = MockStoreView(listOf(* occurrences))) + MockSession.ourBackend.session.controller = controller + return controller + } + + private class MockStoreView(val occurrences: List) : StoreView { + val symbols = occurrences.map { it.constraint().symbol() }.toSet() + + override fun constraintSymbols(): Iterable = symbols + + override fun allOccurrences(): Iterable = occurrences + + override fun occurrences(symbol: ConstraintSymbol): Iterable = + occurrences.filter { it.constraint().symbol() == symbol } + } + + private fun eq(left: T, right: T) = left eq right + + private fun is_eq(left: T, right: T): Boolean = left is_eq right + + @Test + fun processSingle() { + programWithRules( + rule("main1", + headKept( + constraint("main") + ), + body( + constraint("foo") + )) + ).run { + controller().evaluate(occurrence("main")).let { view -> + assertEquals( + setOf(ConstraintSymbol("main", 0), ConstraintSymbol("foo", 0)), + view.allOccurrences().map { it.constraint().symbol() }.toSet()) + } + } + } + + @Test + fun processReplaced() { + programWithRules( + rule("main1", + headKept( + constraint("main") + ), + body( + constraint("foo") + )), + rule("main2", + headKept( + constraint("foo") + ), + headReplaced( + constraint("main") + ), + body( + constraint("bar") + )) + ).run { + controller().evaluate(occurrence("main")).let { view -> + assertEquals( + setOf(ConstraintSymbol("bar", 0), ConstraintSymbol("foo", 0)), + view.allOccurrences().map { it.constraint().symbol() }.toSet()) + } + } + } + + @Test + fun basicExpression() { + var test : String = "not initialized" + programWithRules( + rule("main", + headKept( + constraint("main") + ), + body( + statement { test = "value" } + )) + ).run { + controller().evaluate(occurrence("main")) + assertEquals("value", test) + } + } + + @Test + fun paramExpression() { + var test = logical("X") + //"not initialized" + programWithRules( + rule("main", + headKept( + constraint("main") + ), + body( + statement ({ test.set("value") }) + )) + ).run { + controller().evaluate(occurrence("main")) + assertEquals("value", test.get()) + } + } + + @Test + fun basicLogical() { + var test : String? = "not initialized" + val x = logical("x") + x.setValue("expected") + programWithRules( + rule("main", + headKept( + constraint("main") + ), + body( + statement ({ test = x.get() } ) + )) + ).run { + controller().evaluate(occurrence("main")) + assertEquals("expected", test) + } + } + + @Test + fun logicalCopy() { + var test : String? = "not initialized" + val (x,y) = logical("x", "y") + x.setValue("expected") + programWithRules( + rule("main", + headKept( + constraint("main") + ), + body( + equals(x, y), + constraint("next") + )), + rule("aux", + headKept( + constraint("next") + ), + body( + statement ({ test = y.get() }) + )) + ).run { + controller().evaluate(occurrence("main")).run { + assertEquals( + setOf(ConstraintSymbol("main", 0), ConstraintSymbol("next", 0)), + allOccurrences().map { it.constraint().symbol() }.toSet()) + assertEquals("expected", test) + } + } + } + + @Test + fun basicGuard() { + var test1 : String = "not initialized 1" + var test2 : String = "not initialized 2" + programWithRules( + rule("main1", + headKept( + constraint("main") + ), + guard( + expression { false } + ), + body( + statement { test1 = "not expected" } + )), + rule("main2", + headKept( + constraint("main") + ), + guard( + expression { true } + ), + body( + statement { test2 = "expected" } + )) + ).run { + controller().evaluate(occurrence("main")) + assertEquals("not initialized 1", test1) + assertEquals("expected", test2) + } + } + + @Test + fun basicMetaLogical() { + val (X, Y) = metaLogical("X", "Y") + val a = logical("a") + programWithRules( + rule("rule1", + headReplaced( + constraint("foo", X) + ), + body( + equals(X, Y), + constraint("bar", Y) + ) + ) + ).controller().apply { + a.set("value") }.evaluate(occurrence("foo", a)).run { + assertSame(1, allOccurrences().toList().size) + val co = allOccurrences().first() + assertEquals(ConstraintSymbol("bar",1), co.constraint().symbol()) + assertEquals(1, co.arguments().size) + val arg = co.arguments().first() + assertNotEquals(a, arg) + assertEquals(a.get(), (arg as Logical).get()) + } + } + + @Test + fun occurrenceTerminated() { + programWithRules( + rule("first", + headKept( constraint("foo") ), body( constraint("expected1") ) + ), + rule("second", + headKept( constraint("foo") ), body( constraint("bar") ) + ), + rule("third", + headKept( constraint("foo") ), body( constraint("unexpected") ) + ), + rule("fourth", + headReplaced( constraint("bar"), + constraint("foo") ), + body( constraint("expected2") ) + ) + ).controller().evaluate(occurrence("foo")).run { + assertEquals( + setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0)), + allOccurrences().map { co -> co.constraint().symbol() }.toSet()) + } + } + + @Test + fun occurrenceKeptActive() { + programWithRules( + rule("first", + headKept( constraint("foo") ), body( constraint("bar") ) + ), + rule("second", + headReplaced( constraint("foo") ), body( constraint("expected1") ) + ), + rule("third", + headReplaced( constraint("foo") ), body( constraint("unexpected") ) + ), + rule("fourth", + headKept( constraint("foo") ), + headReplaced( constraint("bar") ), + body( constraint("expected2") ) + ) + ).controller().evaluate(occurrence("foo")).run { + assertEquals( + setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0)), + allOccurrences().map { co -> co.constraint().symbol() }.toSet()) + } + } + + @Test + fun occurrenceReactivated() { + val X = metaLogical("X") + programWithRules( + rule("zeroth", + headKept( constraint("foo") ), body( statement({ x -> x.set(999) }, X), + constraint("bar", X)) + ), + rule("first", + headKept( constraint("foo") ), + body( constraint("bar", X), + constraint("qux", X)) + ), + rule("second", + headReplaced( constraint("qux", X) ), + body( constraint("expected1"), + statement({ x -> x.set(123) }, X)) + ), + rule("third", + headReplaced( constraint("foo") ), + body( constraint("unexpected")) + ), + rule("fourth", + headReplaced( constraint("foo") ), + headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)), + body( constraint("expected2") ) + ), + rule("fifth", + headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 999 }, X)), + body( constraint("expected3", X)) + ) + ).controller().evaluate(occurrence("foo")).run { + assertEquals(3, allOccurrences().count()) + assertEquals( + setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0), ConstraintSymbol("expected3", 1)), + allOccurrences().map { co -> co.constraint().symbol() }.toSet()) + val ex3 = allOccurrences().filter { co -> co.constraint().symbol() == ConstraintSymbol("expected3", 1) }.first() + assertEquals(999, (ex3.arguments().first() as Logical).value()) + } + } + + @Test + fun occurrenceReactivatedAfterUnion() { + val (X, Y) = metaLogical("X", "Y") + programWithRules( + rule("first", + headKept( constraint("foo") ), + body( constraint("bar", X), + constraint("qux", Y), + statement({ x, y -> eq(x, y) }, X, Y)) + ), + rule("second", + headReplaced( constraint("qux", Y) ), + body( constraint("expected1"), + statement({ y -> y.set(123) }, Y)) + ), + rule("third", + headReplaced( constraint("foo") ), + body( constraint("unexpected")) + ), + rule("fourth", + headReplaced( constraint("foo") ), + headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)), + body( constraint("expected2") ) + ), + rule("fifth", + headKept( constraint("bar", X) ), + body( constraint("expected3", X)) + ) + ).controller().evaluate(occurrence("foo")).run { + assertEquals(3, allOccurrences().count()) + assertEquals( + setOf(ConstraintSymbol("expected1", 0), ConstraintSymbol("expected2", 0), ConstraintSymbol("expected3", 1)), + allOccurrences().map { co -> co.constraint().symbol() }.toSet()) + val ex3 = allOccurrences().filter { co -> co.constraint().symbol() == ConstraintSymbol("expected3", 1) }.first() + assertEquals(123, (ex3.arguments().first() as Logical).value()) + } + } + + @Test + fun occurrenceReactivatedAfterUnionUnbound() { + val (X, Y) = metaLogical("X", "Y") + programWithRules( + rule("first", + headKept( constraint("foo") ), + body( constraint("bar", X), + statement({ x, y -> eq(x, y) }, X, Y), + constraint("qux", Y)) + ), + rule("second", + headReplaced( constraint("qux", Y) ), + body( constraint("expected1"), + statement({ y -> y.set(123) }, Y)) + ), + rule("third", + headReplaced( constraint("foo") ), + body( constraint("unexpected")) + ), + rule("fourth", + headReplaced( constraint("foo") ), + headReplaced( constraint("bar", X) ), guard(expression({ x -> x.getNullable() == 123 }, X)), + body( constraint("expected2") ) + ), + rule("fifth", + headKept( constraint("bar", X) ), + body( constraint("expected3", X)) + ) + ).controller().evaluate(occurrence("foo")).run { + assertEquals(3, allOccurrences().count()) + assertEquals(setOf( ConstraintSymbol("expected1", 0), + ConstraintSymbol("expected2", 0), + ConstraintSymbol("expected3", 1)), + allOccurrences().map { co -> co.constraint().symbol() }.toSet()) + val ex3 = allOccurrences().filter { co -> co.constraint().symbol() == ConstraintSymbol("expected3", 1) }.first() + assertEquals(123, (ex3.arguments().first() as Logical).value()) + } + } + + + @Test + fun correctRulesOrder() { + val X= metaLogical("X") + + programWithRules( + rule("main", + headReplaced( constraint("main") ), body( statement({ x -> x.set(1) }, X), + constraint("bar"), + constraint("foo", X) ) + ), + rule("foo_if_zero", + headReplaced( constraint("foo", X) ), guard( expression({ x -> x.get() == 0 }, X) ), + body( constraint("foo_zero") ) + ), + rule("foo_and_bar", + headReplaced( constraint("foo", X) ), + headKept( constraint("bar") ), + body( constraint("foo_and_bar") ) + ), + rule("foo_if_non_zero", + headReplaced( constraint("foo", X) ), + guard( expression({ x -> x.get() != 0 }, X) ), + body( constraint("foo_non_zero") ) + ) + ).controller().evaluate(occurrence("main")).run { + assertEquals(setOf(ConstraintSymbol("bar", 0), ConstraintSymbol("foo_and_bar", 0)), constraintSymbols()) + assertEquals(1, occurrences(ConstraintSymbol("foo_and_bar", 0)).count()) + assertEquals(1, occurrences(ConstraintSymbol("bar", 0)).count()) + } + } + + @Test + fun inverseOccurrenceOrder() { + val (X, Y, Z) = metaLogical("X", "Y", "Z") + val W = metaLogical("W") + + programWithRules( + rule("main", + headReplaced(constraint("main")), body( statement({ w -> w.set(42) }, W), + constraint("foo", W, "a{c}"), + constraint("foo", W, "a{b}"), + constraint("foo", W, "a{d}")) + ), + rule("expected", + headReplaced( + constraint("foo", X, "a{d}"), + constraint("foo", Y, "a{b}"), + constraint("foo", Z, "a{c}")), body(constraint("done"))) + + ).controller().evaluate(occurrence("main")).run { + assertEquals(setOf(ConstraintSymbol("done", 0)), constraintSymbols()) + assertEquals(1, occurrences(ConstraintSymbol.symbol("done", 0)).count()) + } + } + + @Test + fun reactivateOnUnion() { + val (X1,Y1,Z1) = metaLogical("X1", "Y1", "Z1") + val (X2,Y2,Z2) = metaLogical("X2", "Y2", "Z2") + val (X3,Y3,Z3) = metaLogical("X3", "Y3", "Z3") + programWithRules( + rule("main", + headReplaced( constraint("main") ), body( statement({ z -> z.set(0) }, Z1), + constraint("foo", X1, Z1), + constraint("foo", Y1, Z1), + statement({ x, y -> eq(x, y) }, X1, Y1) ) + ), + rule("capture_foo", + headKept( constraint("foo", X2, Y2) ), + body( statement({ y, z -> z.set(y.get() + 1) }, Y2, Z2), + constraint("capture", Z2) ) + ), + rule("capture_foo_foo", + headKept( constraint("foo", X3, Z3) ), + headReplaced( constraint("foo", Y3, Z3) ), guard( expression({ x, y -> is_eq(x, y) }, X3, Y3)), + body( constraint("replaced") ) + ) + ).controller().evaluate(occurrence("main")).run { + assertEquals(setOf( ConstraintSymbol("foo", 2), + ConstraintSymbol("capture", 1), + ConstraintSymbol("replaced", 0)), + constraintSymbols()) + assertEquals(1, occurrences(ConstraintSymbol("foo", 2)).count()) + assertEquals(2, occurrences(ConstraintSymbol("capture", 1)).count()) + assertEquals(1, occurrences(ConstraintSymbol("replaced", 0)).count()) + } + } + + @Test + fun propagationHistory() { + val (X,Y,Z) = metaLogical("X", "Y", "Z") + programWithRules( + rule("main", + headReplaced( constraint("main") ), body( statement({ x, y -> eq(x, y) }, X, Y), // rank(X) = 1 + constraint("foo", Y), + constraint("bar", Z), + // update Z's parent + statement({ x, z -> eq(x, z) }, X, Z) ) + ), + rule("foobar", + headKept( constraint("foo", X) ), + headKept( constraint("bar", Y) ), + body( constraint("foobar") ) + ) + ).controller().evaluate(occurrence("main")).run { + assertEquals(setOf( ConstraintSymbol("foo", 1), + ConstraintSymbol("bar", 1), + ConstraintSymbol("foobar", 0)), + constraintSymbols()) + assertEquals(1, occurrences(ConstraintSymbol("foo", 1)).count()) + assertEquals(1, occurrences(ConstraintSymbol("bar", 1)).count()) + assertEquals(1, occurrences(ConstraintSymbol("foobar", 0)).count()) + } + } + + + @Test + fun removeObserver() { + val (X1,Y1,Z1) = metaLogical("X1", "Y1", "Z1") + val X2 = metaLogical("X2") + val (X3,Y3) = metaLogical("X3", "Y3") + val X4 = metaLogical("X4") + programWithRules( + rule("main", + headReplaced( constraint("main") ), body( statement({ x, y -> eq(x, y) }, X1, Y1), // rank(X) = 1 + statement({ x -> x.set(42) }, X1), + constraint("match", Z1, X1), + constraint("trigger", Z1) ) + ), + rule("trigger", + headReplaced( constraint("trigger", X2) ), + body( constraint("foobar", X2) ) + ), + rule("nofoobar", + headReplaced( constraint("foobar", X3), + constraint("match", X3, Y3) ), + body( constraint("expected"), + constraint("blah"), + statement({ x, z -> eq(x, z) }, X3, Y3) ) + ), + rule("blah", + headReplaced( constraint("blah") ), + headReplaced( constraint("foobar", X4) ), + body( constraint("unexpected") ) + ) + ).controller().evaluate(occurrence("main")).run { + assertEquals(setOf( ConstraintSymbol("blah", 0), + ConstraintSymbol("expected", 0)), + constraintSymbols()) + assertEquals(1, occurrences(ConstraintSymbol("blah", 0)).count()) + assertEquals(1, occurrences(ConstraintSymbol("expected", 0)).count()) + } + } + + @Test + fun reactivateOnUnionKeepValue() { + val (X,Y,Z) = metaLogical("X", "Y", "Z") + programWithRules( + rule("main", + headReplaced( constraint("main") ), body( statement({ x, y -> eq(x, y) }, X, Y), // rank(X) = 1 + statement({ z -> z.set(42) }, Z), + constraint("foo", Z), + statement({ x, z -> eq(x, z) }, X, Z) ) + ), + rule("capture_foo_free", + headKept( constraint("foo", X) ), guard( expression({ x -> x.getNullable() == null }, X) ), + body( constraint("free") ) + ), + rule("capture_foo_assigned", + headKept( constraint("foo", X) ), guard( expression({ x -> x.getNullable() != null }, X) ), + body( constraint("assigned") ) + ) + ).controller().evaluate(occurrence("main")).run { + assertEquals(setOf( ConstraintSymbol("foo", 1), + ConstraintSymbol("assigned", 0)), + constraintSymbols()) + assertEquals(1, occurrences(ConstraintSymbol("foo", 1)).count()) + assertEquals(1, occurrences(ConstraintSymbol("assigned", 0)).count()) + } + } + + @Test + fun firstAlternative() { + val (X, Y) = metaLogical("X", "Y") + + programWithRules( + rule("main", + headReplaced( constraint("main") ), body( statement({ x -> x.set(7) }, X), + statement({ y -> y.set(7) }, Y), + constraint("aux", X, Y) ) + ), + rule("aux", + headReplaced( constraint("aux", X, Y) ), body( equals(X, Y), + constraint("expected") ), + altBody( constraint("unexpected") ) + ) + ).controller().evaluate(occurrence("main")).run { + assertEquals(setOf(ConstraintSymbol("expected", 0)), constraintSymbols()) + assertEquals(1, occurrences(ConstraintSymbol.symbol("expected", 0)).count()) + } + } + + @Test + fun secondAlternative() { + val (X, Y) = metaLogical("X", "Y") + + programWithRules( + rule("main", + headReplaced( constraint("main") ), body( statement({ x -> x.set(7) }, X), + statement({ y -> y.set(13) }, Y), + constraint("aux", X, Y) ) + ), + rule("aux", + headReplaced( constraint("aux", X, Y) ), body( equals(X, Y), + constraint("unexpected") ), + altBody( constraint("expected") ) + ) + ).controller().evaluate(occurrence("main")).run { + assertEquals(setOf(ConstraintSymbol("expected", 0)), constraintSymbols()) + assertEquals(1, occurrences(ConstraintSymbol.symbol("expected", 0)).count()) + } + } + + @Test(expected = EvaluationFailureException::class) + fun lastAlternativeFail() { + val (X, Y, Z) = metaLogical("X", "Y", "Z") + + programWithRules( + rule("main", + headReplaced( constraint("main") ), body( statement({ x -> x.set(7) }, X), + statement({ y -> y.set(13) }, Y), + statement({ z -> z.set(17) }, Z), + constraint("aux", X, Y, Z) ) + ), + rule("aux", + headReplaced( constraint("aux", X, Y, Z) ), body( equals(X, Y), + constraint("unexpected1") ), + altBody( equals(X, Z), + constraint("unexpected2") ) + ) + ).controller().run { + try { + evaluate(occurrence("main")) + } + finally { + assertEquals(emptySet(), storeView().constraintSymbols()) + } + } + } + + +} + diff --git a/reactor/Test/test/TestLogicalObserver.kt b/reactor/Test/test/TestLogicalObserver.kt new file mode 100644 index 00000000..71c1906a --- /dev/null +++ b/reactor/Test/test/TestLogicalObserver.kt @@ -0,0 +1,40 @@ +import jetbrains.mps.logic.reactor.core.LogicalObserver +import jetbrains.mps.logic.reactor.core.LogicalImpl +import jetbrains.mps.logic.reactor.core.addObserver +import jetbrains.mps.logic.reactor.logical.Logical +import org.junit.Test +import org.junit.Assert.* +import java.util.* + +/** + * @author Fedor Isakov + */ + + +class TestLogicalObserver { + + @Test + fun mergeObservers() { + val foo = logical(name = "foo") + val bar = logical(name = "bar") + val bazz = logical(name = "bazz") + + val obs = MockObserver() + foo.addObserver(obs) + bar.addObserver(obs) + bazz.addObserver(obs) + + // bar -> foo + foo.union(bar) + assertEquals(setOf(parent(bar)), obs.getAndClearEvents()) + + // bazz -> foo + bazz.union(foo) + assertEquals(setOf(parent(bazz)), obs.getAndClearEvents()) + + assertSame(bazz.findRoot(), foo) + foo.setValue("test") + assertEquals(setOf(value(foo), value(bar), value(bazz)), obs.getAndClearEvents()) + } + +} diff --git a/reactor/Test/test/TestMatcher.kt b/reactor/Test/test/TestMatcher.kt new file mode 100644 index 00000000..72857291 --- /dev/null +++ b/reactor/Test/test/TestMatcher.kt @@ -0,0 +1,582 @@ +import jetbrains.mps.logic.reactor.core.* +import jetbrains.mps.logic.reactor.evaluation.ConstraintOccurrence +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.program.Constraint +import jetbrains.mps.logic.reactor.program.ConstraintSymbol +import jetbrains.mps.unification.Term +import jetbrains.mps.unification.Unification +import jetbrains.mps.unification.test.MockTermsParser.parse +import org.junit.Assert.* +import org.junit.Ignore +import org.junit.Test + +/** + * @author Fedor Isakov + */ + +class TestMatcher { + + @Test + fun matchSingle() { + programWithRules( + rule("main", + headReplaced( + constraint("main") + ), + body( + constraint("foo") + )) + ).let { builder -> + builder.indices().run { + Matcher(first).matching(occurrence("main"), second).let { matches -> + val match = matches.single() + assertEquals(match.rule, builder.rules.first()) + assertTrue(match.keptOccurrences.isEmpty()) + val occ = match.discardedOccurrences.single() + assert(occ.constraint().symbol().id() == "main") + } + } + } + } + + @Test + fun matchDiscardedKept() { + programWithRules( + rule("main1", + headReplaced( + constraint("main") + ), + body( + constraint("foo") + )), + rule("main2", + headKept( + constraint("main") + ), + body( + constraint("bar") + )) + ).let { builder -> + builder.indices().run { + Matcher(first).matching(occurrence("main"), second).let { matches -> + assertTrue(matches.all {m -> m.successful}) + assertEquals(builder.rules.toSet(), matches.map { m -> m.rule }.toSet()) + matches.forEach { m -> assertTrue(m.keptOccurrences.size + m.discardedOccurrences.size == 1) } + matches.flatMap { m -> + (m.keptOccurrences.toList() + m.discardedOccurrences.toList()) }.forEach { occ -> + assert(occ.constraint().symbol().id() == "main") + } + } + } + } + } + + @Test + fun matchComplementMissing() { + programWithRules( + rule("main1", + headKept( + constraint("main") + ), + headReplaced( + constraint("secondary") + ), + body( + constraint("foo") + )), + rule("main2", + headKept( + constraint("main") + ), + body( + constraint("bar") + )) + ).let { builder -> + builder.indices().run { + Matcher(first).matching(occurrence("main"), second).let { matches -> + assertTrue(matches.all {m -> m.successful}) + assertEquals(builder.rules.drop(1).toSet(), matches.map { m -> m.rule }.toSet()) + } + } + } + } + + @Test + fun matchComplementPresent() { + programWithRules( + rule("main1", + headKept( + constraint("main") + ), + headReplaced( + constraint("aux") + ), + body( + constraint("foo") + )), + rule("main2", + headKept( + constraint("main") + ), + body( + constraint("bar") + )) + ).let { builder -> + builder.indices(occurrence("aux")).run { + Matcher(first).matching(occurrence("main"), second).let { matches -> + assertTrue(matches.all {m -> m.successful}) + assertEquals(builder.rules.toSet(), matches.map { m -> m.rule }.toSet()) + } + } + } + } + + @Test + fun matchArgument() { + programWithRules( + rule("main1", + headKept( + constraint("main", "foo") + ), + body( + constraint("foo") + )), + rule("main2", + headKept( + constraint("main", "bar") + ), + body( + constraint("bar") + )) + ).let { builder -> + builder.indices().run{ + Matcher(first).matching(occurrence("main", "bar"), second).let { matches -> + assertTrue(matches.all {m -> m.successful}) + assertEquals(builder.rules.drop(1), matches.map { m -> m.rule }.toList()) + } + } + } + } + + @Test + fun noMatchArgument() { + programWithRules( + rule("main1", + headKept( + constraint("main", "foo") + ), + body( + constraint("foo") + )), + rule("main2", + headKept( + constraint("main", "bar") + ), + body( + constraint("bar") + )) + ).indices().run{ + Matcher(first).matching(occurrence("main", "qux"), second).let { matches -> + assertFalse(matches.any()) + } + } + } + + @Test + fun multipleHandlers() { + programWithHandlers( + handler("handler1", listOf(ConstraintSymbol("foo", 0)), + rule("main1", + headKept( + constraint("foo") + )) + ), + handler("handler2", listOf(ConstraintSymbol("bar", 0)), + rule("main2", + headKept( + constraint("bar") + )) + ) + ).indices().run { + Matcher(first).matching(occurrence("qux"), second).let { matches -> + assertFalse(matches.any()) + } + Matcher(first).matching(occurrence("foo"), second).let { matches -> + assertSame(1, matches.size) + } + Matcher(first).matching(occurrence("bar"), second).let { matches -> + assertSame(1, matches.size) + } + } + } + + @Test + fun metaLogical() { + val (A, B, C) = metaLogical("A", "B", "C") + val b = B.logical() + + programWithRules( + rule("main", + headKept( + constraint("foo", A, B) + ), + body( + constraint("bar", B) + ) + ) + ).indices().run { + Matcher(first).matches(occurrence("foo", "blah", b), second).first().run { + assert(successful) + assertEquals("blah", logicalContext.variable(A).findRoot().value()) + assertSame(b, logicalContext.variable(B)) + assertEquals(C.logical().metaLogical(), logicalContext.variable(C).metaLogical()) + } + } + + } + + @Test + fun matchMetaLogical() { + val (M, N) = metaLogical("M", "N") + + programWithRules( + rule("main1", + headKept( + constraint("foo", M) + ), + headReplaced( + constraint("foo", N) + ), + body( + constraint("bar") + )), + rule("main2", + headKept( + constraint("foo", M) + ), + headReplaced( + constraint("foo", M) + ), + body( + constraint("bar") + )) + ).run { + indices().run { + Matcher(first).matching(occurrence("foo", 1), second).let { matches -> + assertFalse(matches.any()) + } + } + + // same parameter -- 4 matches (all permutations) + indices(occurrence("foo", 42)).run { + Matcher(first).matching(occurrence("foo", 42), second).let { matches -> + assertTrue(matches.all {m -> m.successful}) + assertEquals(4, matches.count()) + assertTrue(matches.all { m -> m.allOccurrences().toSet().size == 2 }) + } + } + + indices(occurrence("foo", 42)).run { + Matcher(first).matching(occurrence("foo", 16), second).let { matches -> + assertTrue(matches.all {m -> m.successful}) + assertEquals(2, matches.count()) + assertEquals(listOf("main1", "main1"), matches.map { m -> m.rule.tag() }.toList()) + matches.map { m -> + setOf(M, N).map { lp -> + m.logicalContext.variable(lp).findRoot().value() + } + }.forEach { vals -> + assertEquals(setOf(42, 16), vals.toSet()) + } + assertTrue(matches.all { m -> m.allOccurrences().toSet().size == 2 }) + } + } + + val (x, y) = logical("x", "y") + x.set(123) + y.set(456) + + indices(occurrence("foo", x)).run { + Matcher(first).matching(occurrence("foo", y), second).let { matches -> + assertTrue(matches.all {m -> m.successful}) + assertEquals(2, matches.count()) + assertEquals(listOf("main1", "main1"), matches.map { m -> m.rule.tag() }.toList()) + assertTrue(matches.all { m -> m.allOccurrences().toSet().size == 2 }) + } + } + + val (v, w) = logical("v", "w") + w.findRoot().union(v) + + indices(occurrence("foo", v)).run { + Matcher(first).matching(occurrence("foo", w), second).let { matches -> + assertTrue(matches.all {m -> m.successful}) + assertEquals(2, matches.count()) + assertTrue(matches.all{ m -> m.allOccurrences().toSet().size == 2 }) + } + } + } + } + + @Test + fun matchOccurrenceArguments() { + val (M, N) = metaLogical("M", "N") + val (O, P) = metaLogical("O", "P") + + programWithRules( + rule("select_A_x", + headKept( + constraint("foo", "A", M) + ), + headReplaced( + constraint("foo", "A", N) + ), + body( + constraint("bar") + )), + rule("select_A_B", + headReplaced( + constraint("foo", "A", "B") + ), + body( + constraint("bar") + )), + rule("select_x_B", + headKept( + constraint("foo", M, "B") + ), + body( + constraint("bar") + )), + rule("select_B_x", + headKept( + constraint("foo", "B", M) + ), + body( + constraint("bar") + )), + rule("select_ALL", + headKept( + constraint("foo", O, P) + ), + body( + constraint("bar") + )) + ).indices().first.run { + val x = logical("X") + + assertEquals( + listOf(byTag("select_A_x"), byTag("select_A_B"), byTag("select_x_B"), byTag("select_ALL")), + forOccurrence(occurrence("foo", "A", x)).toList()) + assertEquals( + listOf(byTag("select_x_B"), byTag("select_B_x"), byTag("select_ALL")), + forOccurrence(occurrence("foo", "B", x)).toList()) + assertEquals( + listOf(byTag("select_B_x"), byTag("select_ALL")), + forOccurrence(occurrence("foo", "B", "C")).toList()) + + } + } + + @Test + fun matchOccurrenceTermArguments() { + val (M, N) = metaLogical("M", "N") + val (O, P) = metaLogical("O", "P") + + programWithRules( + rule("select_fg_x", + headKept( + constraint("foo", parse("f{g}"), M) + ), + body( + constraint("bar") + )), + rule("select_abX_x", + headKept( + constraint("foo", parse("a{b X}"), M) + ), + body( + constraint("bar") + )), + rule("select_aYcde_x", + headKept( + constraint("foo", parse("a{Y c{d e}}"), M) + ), + body( + constraint("bar") + )), + rule("select_aYcdd_fg", + headKept( + constraint("foo", parse("a{Y c{d d}}"), parse("f{g}")) + ), + body( + constraint("bar") + )) + ).indices().first.run { + + assertEquals( + listOf(byTag("select_fg_x")), + forOccurrence(occurrence("foo", parse("f{g}"), parse("u"))).toList()) + assertEquals( + listOf(byTag("select_fg_x")), + forOccurrence(occurrence("foo", parse("f{Z}"), parse("u"))).toList()) + assertEquals( + listOf(byTag("select_abX_x"), byTag("select_aYcde_x")), + forOccurrence(occurrence("foo", parse("a{b c{d Z}}"), parse("u"))).toList()) + assertEquals( + listOf(byTag("select_aYcdd_fg")), + forOccurrence(occurrence("foo", parse("a{d c{d d}}"), parse("f{g}"))).toList()) + + } + } + + @Test + fun matchTermArguments() { + programWithRules( + rule("select_abcde", + headKept( + constraint("foo", parse("a{b{h} c{d e}}")) + ), + body( + constraint("bar") + )), + rule("select_abcdf", + headKept( + constraint("foo", parse("a{b{h} c{d f}}")) + ), + body( + constraint("bar") + )), + rule("select_agcde", + headKept( + constraint("foo", parse("a{g{h} c{d e}}")) + ), + body( + constraint("bar") + )) + ).indices().first.run { + + assertEquals( + listOf(byTag("select_abcde")), + forOccurrence(occurrence("foo", parse("a{b{h} c{d e}}"))).toList()) + assertEquals( + listOf(byTag("select_abcdf")), + forOccurrence(occurrence("foo", parse("a{b{h} c{d f}}"))).toList()) + assertEquals( + listOf(byTag("select_agcde")), + forOccurrence(occurrence("foo", parse("a{g{h} c{d e}}"))).toList()) + + } + } + + @Test + fun matchConstraintsWrongOrder() { + + val (b, c) = metaLogical("b", "c") + val (x, y) = metaLogical("x", "y") + + val program = programWithRules( + rule("foo2", + headKept( + constraint("foo", b, parse("a{b}")), + constraint("foo", c, parse("a{c}")) + ), + body( + constraint("done") + ) + ) + ) + + program.indices(occurrence("foo", x, parse("a{c}"))).run { + Matcher(first).matching(occurrence("foo", y, parse("a{b}")), second).let { matches -> + assertEquals("foo2", matches.single().rule.tag()) + } + } + program.indices(occurrence("foo", x, parse("a{b}"))).run { + Matcher(first).matching(occurrence("foo", y, parse("a{c}")), second).let { matches -> + assertEquals("foo2", matches.single().rule.tag()) + } + } + } + + @Test + fun matchConstraintsIncrementally() { + + val (b, c, d) = metaLogical("b", "c", "d") + val (x, y, z) = metaLogical("x", "y", "z") + + val program = programWithRules( + rule("expected", + headKept( + constraint("foo", d, parse("a{d}")), + constraint("foo", b, parse("a{b}")), + constraint("foo", c, parse("a{c}")) + ), + body( + constraint("done") + ) + ) + ) + + val stored = ArrayList() + program.indices(stored).run { + val matcher = Matcher(first) + val occ1 = occurrence("foo", x, parse("a{c}")) + stored.add(occ1) + matcher.matching(occ1, second).let { matches -> + assertTrue(matches.isEmpty()) + } + val occ2 = occurrence("foo", y, parse("a{b}")) + stored.add(occ2) + matcher.matching(occ2, second).let { matches -> + assertTrue(matches.isEmpty()) + } + val occ3 = occurrence("foo", z, parse("a{d}")) + stored.add(occ3) + matcher.matching(occ3, second).let { matches -> + assertEquals("expected", matches.single().rule.tag()) + } + } + } + + private fun Builder.indices(vararg occurrence: ConstraintOccurrence): Pair = + this.indices(occurrence.toList()) + + private fun Builder.indices(stored: List): Pair { + + val aux = object : OccurrenceIndex { + override fun forSymbol(symbol: ConstraintSymbol): Iterable = + stored.filter { co -> co.constraint().symbol() == symbol } + + override fun forLogical(logical: Logical<*>): Iterable = + stored.filter { co -> + co.arguments().any { it is Logical<*> && it.isBound && it.findRoot() == logical.findRoot() } + } + + override fun forLogicalAndConstraint(logical: Logical<*>, cst: Constraint): Iterable = + stored.filter { co -> + co.arguments().any { it is Logical<*> && it.isBound && it.findRoot() == logical.findRoot() } + } + + override fun forTerm(term: Term): Iterable = + stored.filter { co -> + co.arguments().any { it is Term && Unification.unify(it, term).isSuccessful } + } + + override fun forTermAndConstraint(term: Term, cst: Constraint): Iterable = + stored.filter { co -> + co.constraint().symbol() == cst.symbol() && co.arguments().any { it is Term && Unification.unify(it, term).isSuccessful } + } + + override fun forValue(value: Any): Iterable = + stored.filter { co -> co.arguments().contains(value) } + } + + return RuleIndex(handlers).to(aux) + } + + private fun Match.allOccurrences() = (keptOccurrences + discardedOccurrences) + + private fun Matcher.matching(activeOcc: ConstraintOccurrence, aux: OccurrenceIndex) = this.matches(activeOcc, aux).filter { m -> m.successful } + +} diff --git a/reactor/Test/test/TestOccurrenceStore.kt b/reactor/Test/test/TestOccurrenceStore.kt new file mode 100644 index 00000000..2bc831a3 --- /dev/null +++ b/reactor/Test/test/TestOccurrenceStore.kt @@ -0,0 +1,153 @@ +import jetbrains.mps.logic.reactor.core.* +import jetbrains.mps.logic.reactor.logical.Logical +import jetbrains.mps.logic.reactor.program.ConstraintSymbol.symbol +import jetbrains.mps.logic.reactor.util.emptyConsList +import jetbrains.mps.unification.Term +import jetbrains.mps.unification.test.MockTermsParser.parse +import org.junit.Test +import org.junit.Assert.* +import org.junit.Before + +/** + * @author Fedor Isakov + */ + +class TestOccurrenceStore { + + internal class MockProxy(val _store: () -> Store) : LogicalObserver, StoreKeeper { + + private var observerList = emptyConsList, LogicalObserver>>() + + override fun store(): Store = _store() + + override fun addObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) { + if (!observerList.any { obs -> obs.first === logical }) { // referential equality! + logical.addObserver(this) + } + this.observerList = observerList.prepend(logical.to(obs(this))) + } + + override fun removeObserver(logical: Logical<*>, obs: (StoreKeeper) -> LogicalObserver) = TODO() + + override fun valueUpdated(logical: Logical<*>) { + for (obs in observerList) { + if (obs.first === logical) { // referential equality! + obs.second.valueUpdated(logical) + } + } + } + + override fun parentUpdated(logical: Logical<*>) { + for (obs in observerList) { + if (obs.first === logical) { // referential equality! + obs.second.parentUpdated(logical) + } + } + } + } + + lateinit var occstore: Store + + @Before + fun setup() { + occstore = Store { MockProxy { occstore } } + } + + @Test + fun testMergeLogicals() { + val foo = logical("foo") + val bar = logical("bar") + + val main = occurrence("main", foo) + occstore.store(main) + assertEquals(listOf(main), occstore.forLogical(foo)) + assertEquals(listOf(main), occstore.forSymbol(symbol("main", 1))) + + bar.union(foo) + assertEquals(listOf(main), occstore.forLogical(foo)) + assertEquals(listOf(main), occstore.forLogical(bar)) + + val bazz = logical("bazz") + val main2 = occurrence("main", bazz) + occstore.store(main2) + assertEquals(listOf(main2), occstore.forLogical(bazz)) + + bazz.union(bar) + assertEquals(setOf(main, main2), occstore.forLogical(bazz).toSet()) + assertEquals(occstore.forLogical(foo), occstore.forLogical(bazz)) + assertEquals(occstore.forLogical(bar), occstore.forLogical(bazz)) + + occstore.discard(main) + assertEquals(listOf(main2), occstore.forLogical(foo)) + assertEquals(listOf(main2), occstore.forLogical(bar)) + assertEquals(listOf(main2), occstore.forLogical(bazz)) + } + + @Test + fun testLateGroundLogical() { + val foo = logical("foo") + val bar = logical("bar") + + val cnst = occurrence("cnst", foo) + occstore.store(cnst) + assertEquals(listOf(cnst), occstore.forLogical(foo)) + assertEquals(listOf(cnst), occstore.forSymbol(symbol("cnst", 1))) + + foo.set(parse("a{b c d{e}}")) + + assertEquals(listOf(cnst), occstore.forLogical(foo)) + assertEquals(listOf(cnst), occstore.forTerm(parse("a{b c d{e}}"))) + + foo.union(bar) + assertEquals(listOf(cnst), occstore.forLogical(bar)) + } + + @Test + fun testEarlyGroundLogical() { + val foo = logical("foo") + + foo.set(parse("a{b c d{e}}")) + + val cnst = occurrence("cnst", foo) + occstore.store(cnst) + + assertEquals(listOf(cnst), occstore.forLogical(foo)) + assertEquals(listOf(cnst), occstore.forSymbol(symbol("cnst", 1))) + assertEquals(listOf(cnst), occstore.forTerm(parse("a{b c d{e}}"))) + } + + @Test + fun testIndependentlyGroundLogical() { + val foo = logical("foo") + + val cnst = occurrence("cnst", parse("a{b c d{e}}")) + occstore.store(cnst) + + foo.set(parse("a{b c d{e}}")) + + assertEquals(listOf(cnst), occstore.forSymbol(symbol("cnst", 1))) + assertEquals(listOf(cnst), occstore.forTerm(parse("a{b c d{e}}"))) + assertEquals(listOf(cnst), occstore.forLogical(foo)) + } + + @Test + fun testValueIndex () { + val value = "foobar" + val main = occurrence("main", value) + occstore.store(main) + + assertEquals(listOf(main), occstore.forValue(value)) + } + + @Test + fun testTermIndex () { + val foo = occurrence("foo", parse("a{b c}")) + occstore.store(foo) + + // TODO: more tests + assertEquals(listOf(foo), occstore.forTerm(parse("a{b c}"))) + assertEquals(listOf(foo), occstore.forTerm(parse("a{b Y}"))) + assertEquals(listOf(foo), occstore.forTerm(parse("Z"))) + } + +} diff --git a/reactor/Test/test/TestProgram.kt b/reactor/Test/test/TestProgram.kt new file mode 100644 index 00000000..ea82ae35 --- /dev/null +++ b/reactor/Test/test/TestProgram.kt @@ -0,0 +1,176 @@ +import jetbrains.mps.logic.reactor.core.EvaluationSessionImpl +import jetbrains.mps.logic.reactor.evaluation.EvaluationSession +import jetbrains.mps.logic.reactor.evaluation.StoreView +import jetbrains.mps.logic.reactor.logical.Logical +import solver.MockSessionSolver +import jetbrains.mps.logic.reactor.program.ConstraintSymbol +import jetbrains.mps.logic.reactor.program.JavaPredicateSymbol +import jetbrains.mps.logic.reactor.program.PredicateSymbol +import org.junit.* +import org.junit.Assert.* +import program.MockConstraint +import solver.eq + +/** + * @author Fedor Isakov + */ + +class TestProgram { + + companion object { + @BeforeClass @JvmStatic fun setup() { + EvaluationSessionImpl.init(); + } + @AfterClass @JvmStatic fun teardown() { + EvaluationSessionImpl.deinit(); + } + } + + private fun Builder.session(name: String): StoreView { + val sessionSolver = MockSessionSolver(env.expressionSolver, env.equalsSolver) + val programBuilder = ProgramBuilder(MockConstraintRegistry(sessionSolver)) + for (h in handlers) { + programBuilder.addHandler(h) + } + val session = EvaluationSession.newSession(programBuilder.program(name)). + withPredicates(PredicateSymbol("equals", 2), JavaPredicateSymbol.EXPRESSION0, JavaPredicateSymbol.EXPRESSION1, JavaPredicateSymbol.EXPRESSION2, JavaPredicateSymbol.EXPRESSION3). + withParam("main", MockConstraint(ConstraintSymbol("main", 0))).start(sessionSolver) + return session.storeView() + } + + @Test + fun replace() { + programWithRules( + rule("main", + headReplaced( + constraint("main") + ), + body( + constraint("foo") + )), + rule("main.foo", + headKept( + constraint("foo") + ), + body( + constraint("bar") + ) + ) + ).session("replace").run { + assertEquals(setOf(ConstraintSymbol("foo", 0), ConstraintSymbol("bar", 0)), constraintSymbols()) + } + } + + @Test + fun logicalValue() { + val (X, Y, Z) = metaLogical("X", "Y", "Z") + programWithRules( + rule("main", + headReplaced( + constraint("main") + ), + body( + statement({ z -> z eq 33 }, Z), + constraint("foo", Z) + ) + ), + rule("rule1", + headKept( + constraint("foo", X) + ), + body( + statement({x, y -> y eq (x.get() * 2) }, X, Y), + constraint("bar", Y) + ) + ) + ).session("logicalValue").run { + assertEquals(setOf(ConstraintSymbol("foo", 1), ConstraintSymbol("bar", 1)), constraintSymbols()) + assertEquals(2, allOccurrences().count()) + val yval = occurrences(ConstraintSymbol("bar", 1)).first().arguments().first() + assertEquals(66, (yval as Logical).get()) + } + } + + @Test + fun simpleProgram() { + val (X, Y) = metaLogical("X", "Y") + + programWithRules( + rule("main", + headReplaced( constraint("main") ), body( statement({ x -> x.set(5) }, X), + constraint("val", X) ) + ), + rule("dec", + headReplaced( constraint("val", X) ), guard( expression({ x -> x.get() > 0 }, X) ), + body( constraint("trail", X), + statement({ x, y -> y.set(x.get() - 1)}, X, Y), + constraint("val", Y) ) + ) + ).session("dec").run { + assertEquals(setOf(ConstraintSymbol("val", 1), ConstraintSymbol("trail", 1)), constraintSymbols()) + assertEquals(1, occurrences(ConstraintSymbol.symbol("val", 1)).count()) + val a = occurrences(ConstraintSymbol.symbol("val", 1)).first().arguments().first() + assertEquals(0, (a as Logical).get()) + assertEquals(5, occurrences(ConstraintSymbol.symbol("trail", 1)).count()) + } + } + + @Test + fun gcd() { + val (M, N, TMP) = metaLogical("M", "N", "TMP") + programWithRules( + rule("main", + headReplaced( constraint("main") ), body( statement({ m, n -> m.set(21); n.set(35) }, M, N), + constraint("gcd", M), + constraint("gcd", N) ) + ), + rule("trivial", + headReplaced( constraint("gcd", M) ), guard( expression({ x -> x.get() == 0 }, M) ), + body( statement { } /*nothing*/ ) + ), + rule("step", + headKept( constraint("gcd", N) ), + headReplaced( constraint("gcd", M) ), + guard( expression({ m, n -> m.get() >= n.get()}, M, N) ), + body( statement({ m, n, tmp -> tmp.set(m.get() - n.get())}, M, N, TMP), + constraint("gcd", TMP) + ) + ) + ).session("gcd").run { + assertEquals(1, allOccurrences().count()) + val arg = allOccurrences().first().arguments().first() + assertEquals(7, (arg as Logical).get()) + } + } + + + @Test + fun primes() { + val (M, N) = metaLogical("M", "N") + programWithRules( + rule("main", + headReplaced( constraint("main") ), body( statement({ n -> n.set(10) }, N), + constraint("prime", N) ) + ), + rule("gen", + headKept( constraint("prime", N)), guard( expression({ n -> n.get() > 2 }, N)), + body( statement({ m, n -> m.set(n.get() - 1) }, M, N), + constraint("prime", M) ) + ), + rule("sift", + headKept( constraint("prime", M)), + headReplaced( constraint("prime", N)), + guard( expression({ m, n -> (n.get() % m.get()) == 0 }, M, N)), + body( statement { } /* nothing */ ) + ) + ).session("primes").run { + assertEquals(4, allOccurrences().count()) + assertEquals(setOf(2,3,5,7), occurrences(ConstraintSymbol.symbol("prime", 1)). + flatMap { co -> co.arguments() }. + map { a -> (a as Logical).findRoot().value() }. + toSet()) + } + + } + +} diff --git a/reactor/Test/test/TestProgramBuilder.kt b/reactor/Test/test/TestProgramBuilder.kt new file mode 100644 index 00000000..c44e161e --- /dev/null +++ b/reactor/Test/test/TestProgramBuilder.kt @@ -0,0 +1,84 @@ +import jetbrains.mps.logic.reactor.evaluation.AbstractSolver +import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation +import jetbrains.mps.logic.reactor.program.InvalidConstraintException +import jetbrains.mps.logic.reactor.program.InvalidRuleException +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import solver.MockSessionSolver + +/** + * @author Fedor Isakov + */ + +class TestProgramBuilder { + + val dummySolver = object : AbstractSolver() { + override fun ask(invocation: PredicateInvocation?): Boolean = TODO() + override fun tell(invocation: PredicateInvocation?) = TODO() + } + + val sessionSolver = MockSessionSolver(dummySolver, dummySolver) + + @Before fun beforeTest() { + programBuilder = ProgramBuilder(MockConstraintRegistry(sessionSolver)) + } + + lateinit var programBuilder: ProgramBuilder + + @Test(expected = InvalidRuleException::class) + fun emptyBody() { + programWithRules(programBuilder, + rule("foo", + headKept( + constraint("bar") + ))).run { + + programBuilder.addHandler(MockHandler("test", emptyList(), rules)) + } + } + + @Test + fun replace() { + programWithRules(programBuilder, + rule("foo", + headReplaced( + constraint("bar") + ), + body( + constraint("baz") + )), + rule("qux", + headReplaced( + constraint("bar") + ), + headKept( + constraint("quux") + ), + body( + constraint("blah") + ))).run { + + programBuilder.addHandler(MockHandler("test", emptyList(), rules)) + assertEquals(programBuilder.program("test").handlers().flatMap { it.rules() }.count(), 2) + } + } + + @Test(expected = InvalidConstraintException::class) + fun fail() { + programWithRules(programBuilder, + rule("foo", + headReplaced( + constraint("bar", 1) + ), + body( + constraint("bar", "1") + ))).run { + + programBuilder.addHandler(MockHandler("test", emptyList(), rules)) + } + } +} + + + diff --git a/reactor/Test/test/TestTermTrie.kt b/reactor/Test/test/TestTermTrie.kt new file mode 100644 index 00000000..550711f3 --- /dev/null +++ b/reactor/Test/test/TestTermTrie.kt @@ -0,0 +1,321 @@ +import jetbrains.mps.logic.reactor.core.TermTrie +import jetbrains.mps.unification.Term +import jetbrains.mps.unification.test.MockTerm +import jetbrains.mps.unification.test.MockTerm.* +import jetbrains.mps.unification.test.MockTermsParser +import jetbrains.mps.unification.test.MockTermsParser.parse +import org.junit.Test +import org.junit.Assert.* + + +/** + * @author Fedor Isakov + */ + +class TestTermTrie { + + @Test + fun testPut() { + val t1 = parse("a{b c}") + val t2 = parse("a{d e}") + val t3 = parse("f{g h{i k{l m{o p} n}}}") + val t4 = parse("f{g h{i q }}") + + val trie1 = TermTrie().runs( + { put(t1, "foo") }, + { put(t2, "bar") }, + { put(t3, "qux") }, + { put(t4, "blah") } + ) + + assertEquals(setOf("foo", "bar", "qux", "blah"), trie1.allValues().toSet()) + assertEquals(setOf("bar"), trie1.lookupValues(t2).toSet()) + + val trie2 = trie1.put(t2, "bazz") + assertEquals(setOf("foo", "bar", "qux", "blah", "bazz"), trie2.allValues().toSet()) + + assertEquals(setOf("bar", "bazz"), trie2.lookupValues(t2).toSet()) + assertEquals(setOf(), trie2.lookupValues(parse("a{d f}")).toSet()) + assertEquals(setOf("qux"), trie2.lookupValues(t3).toSet()) + assertEquals(setOf("blah"), trie2.lookupValues(t4).toSet()) + + val trie3 = trie2.put(t4, "shmoo") + assertEquals(setOf("foo", "bar", "qux", "blah", "bazz", "shmoo"), trie3.allValues().toSet()) + assertEquals(setOf("blah", "shmoo"), trie3.lookupValues(t4).toSet()) + } + + + @Test + fun testRemove() { + val t1 = parse("a{b c}") + val t2 = parse("a{d e}") + val t3 = parse("f{g h{i k{l m{o p} n}}}") + val t4 = parse("f{g h{i q }}") + + val trie1 = TermTrie().runs( + { put(t1, "foo") }, + { put(t2, "bar") }, + { put(t3, "qux") }, + { put(t4, "blah") } + ) + + assertEquals(setOf("foo", "bar", "qux", "blah"), trie1.allValues().toSet()) + assertEquals(setOf("bar"), trie1.lookupValues(t2).toSet()) + + val trie2 = trie1.remove(t2, "bazz") + assertEquals(setOf("foo", "bar", "qux", "blah"), trie2.allValues().toSet()) + assertEquals(setOf("bar"), trie2.lookupValues(t2).toSet()) + + val trie3 = trie2.remove(t2, "bar") + assertEquals(setOf("foo", "qux", "blah"), trie3.allValues().toSet()) + assertEquals(setOf(), trie3.lookupValues(t2).toSet()) + + + val trie4 = trie3.remove(t3, "qux") + assertEquals(setOf("foo", "blah"), trie4.allValues().toSet()) + assertEquals(setOf(), trie4.lookupValues(t3).toSet()) + assertEquals(setOf("blah"), trie4.lookupValues(t4).toSet()) + } + + + @Test + fun testRemovePut() { + val t1 = parse("a{b c}") + val t2 = parse("a{b d}") + + val trie1 = TermTrie().runs( + { put(t1, "foo") }, + { put(t2, "bar") } + ) + + assertEquals(setOf("foo", "bar"), trie1.allValues().toSet()) + assertEquals(setOf("foo"), trie1.lookupValues(t1).toSet()) + assertEquals(setOf("bar"), trie1.lookupValues(t2).toSet()) + + val trie2 = trie1.remove(t2, "bar") + assertEquals(setOf("foo"), trie2.allValues().toSet()) + assertEquals(setOf("foo"), trie2.lookupValues(t1).toSet()) + assertEquals(setOf(), trie2.lookupValues(t2).toSet()) + + val trie3 = trie2.put(t2, "bazz") + assertEquals(setOf("foo", "bazz"), trie3.allValues().toSet()) + assertEquals(setOf("foo"), trie3.lookupValues(t1).toSet()) + assertEquals(setOf("bazz"), trie3.lookupValues(t2).toSet()) + + val trie4 = trie3.remove(t1, "foo") + assertEquals(setOf("bazz"), trie4.allValues().toSet()) + assertEquals(setOf(), trie4.lookupValues(t1).toSet()) + assertEquals(setOf("bazz"), trie4.lookupValues(t2).toSet()) + } + + + @Test + fun testUnif () { + + val t1 = parse("f{g{a b} a}") + val t2 = parse("f{g{a X} c}") + val t3 = parse("f{g{b c} X}") + val t4 = parse("f{g{X b} X}") + val t5 = parse("f{X Y}") + + val tt = TermTrie().runs( + { put(t1, "t1") }, + { put(t2, "t2") }, + { put(t3, "t3") }, + { put(t4, "t4") }, + { put(t5, "t5") } + ) + + assertEquals(setOf("t3", "t5"), tt.lookupValues(parse("f{g{b c} a}")).toSet()) + assertEquals(setOf("t3", "t4", "t5"), tt.lookupValues(parse("f{g{b X} a}")).toSet()) + assertEquals(setOf("t1", "t3", "t4", "t5"), tt.lookupValues(parse("f{g{X Y} a}")).toSet()) + + val tt2 = tt.remove(t4, "t4") + assertEquals(setOf("t3", "t5"), tt2.lookupValues(parse("f{g{b X} a}")).toSet()) + assertEquals(setOf("t1", "t3", "t5"), tt2.lookupValues(parse("f{g{X Y} a}")).toSet()) + + } + + + @Test + fun testQuirks() { + val t1 = parse("a{b c}") + val t2 = parse("b{c d}") + + val trie1 = TermTrie().runs ( + { put(t1, "foo") }, + { put(t2, "bar") } + ) + + assertEquals(setOf("foo"), trie1.lookupValues(parse("a{b c d}")).toSet()) + assertEquals(setOf("bar"), trie1.lookupValues(parse("b{c d e}")).toSet()) + assertEquals(setOf(), trie1.lookupValues(parse("a{b}")).toSet()) + assertEquals(setOf(), trie1.lookupValues(parse("b{c}")).toSet()) + } + + + @Test + fun testWildcard() { + val t1 = parse("a{X c}") + val t2 = parse("b{c Y}") + + val trie1 = TermTrie().runs( + { put(t1, "foo") }, + { put(t2, "bar") } + ) + + assertEquals(setOf("foo"), trie1.lookupValues(parse("a{b c}")).toSet()) + assertEquals(setOf("foo"), trie1.lookupValues(parse("a{b c d}")).toSet()) + assertEquals(setOf("foo"), trie1.lookupValues(parse("a{Z c}")).toSet()) + assertEquals(setOf("bar"), trie1.lookupValues(parse("b{c d e}")).toSet()) + assertEquals(setOf("bar"), trie1.lookupValues(parse("b{c d}")).toSet()) + assertEquals(setOf("bar"), trie1.lookupValues(parse("b{c Z}")).toSet()) + assertEquals(setOf(), trie1.lookupValues(parse("a{b}")).toSet()) + // invariant violated: fixed term arity +// assertEquals(setOf(), trie1.lookupValues(parse("a{Z}")).toSet()) +// assertEquals(setOf(), trie1.lookupValues(parse("b{c}")).toSet()) +// assertEquals(setOf(), trie1.lookupValues(parse("b{Z}")).toSet()) + } + + + @Test + fun testVariable() { + val t1 = parse("a{b c}") + val t2 = parse("b{c d}") + val t3 = parse("f{g h{i k{l m{o p} n}}}") + val t4 = parse("f{g h{i q }}") + + val trie1 = TermTrie().runs( + { put(t1, "foo") }, + { put(t2, "bar") }, + { put(t3, "qux") }, + { put(t4, "blah") } + ) + + assertEquals(setOf("foo"), trie1.lookupValues(parse("a{X c}")).toSet()) + assertEquals(setOf("foo"), trie1.lookupValues(parse("a{b Y}")).toSet()) + assertEquals(setOf("foo"), trie1.lookupValues(parse("a{X Y}")).toSet()) + assertEquals(setOf("foo", "bar", "qux", "blah"), trie1.lookupValues(parse("Z")).toSet()) + assertEquals(setOf("qux"), trie1.lookupValues(parse("f{X h{i k{l Y Z}}}")).toSet()) + assertEquals(setOf("qux", "blah"), trie1.lookupValues(parse("f{g h{i X}}")).toSet()) + } + + + @Test + fun testWildcardVariable() { + val t1 = parse("a{X c{d e}}") + val t2 = parse("a{b Y}") + val t3 = parse("a{X Y}") + val t4 = parse("f{g h{Z k{l m{o p} n}}}") + val t5 = parse("f{Z h{i q }}") + + val trie1 = TermTrie().runs( + { put(t1, "foo") }, + { put(t2, "bar") }, + { put(t3, "bazz") }, + { put(t4, "qux") }, + { put(t5, "blah") } + ) + + assertEquals(setOf("foo", "bar", "bazz"), trie1.lookupValues(parse("a{X c{d e}}")).toSet()) + assertEquals(setOf("foo", "bar", "bazz"), trie1.lookupValues(parse("a{b Y}")).toSet()) + assertEquals(setOf("foo", "bazz"), trie1.lookupValues(parse("a{c Y}")).toSet()) + assertEquals(setOf("foo", "bar", "bazz"), trie1.lookupValues(parse("a{X Y}")).toSet()) + assertEquals(setOf("foo", "bar", "bazz", "qux", "blah"), trie1.lookupValues(parse("Z")).toSet()) + assertEquals(setOf("qux"), trie1.lookupValues(parse("f{X h{i k{l Y Y}}}")).toSet()) + assertEquals(setOf("qux"), trie1.lookupValues(parse("f{X h{j Y}}")).toSet()) + assertEquals(setOf("qux", "blah"), trie1.lookupValues(parse("f{g h{i X}}")).toSet()) + assertEquals(setOf("qux", "blah"), trie1.lookupValues(parse("f{X h{i Y}}")).toSet()) + assertEquals(setOf("qux", "blah"), trie1.lookupValues(parse("f{X Y}")).toSet()) + assertEquals(setOf("blah"), trie1.lookupValues(parse("f{j h{X q}}")).toSet()) + } + + + @Test + fun testValuesOrder() { + val t1 = parse("a{X c}") + val t2 = parse("a{b Y}") + val t3 = parse("a{c Y}") + val t4 = parse("a{X Y}") + + val trie1 = TermTrie().runs( + { put(t1, "foo") }, + { put(t2, "bar") }, + { put(t3, "bazz") }, + { put(t4, "qux") } + ) + + // value order/cardinality no longer maintained + assertEquals(setOf("foo", "bar", "qux"), trie1.lookupValues(parse("a{b X}")).toSet()) + assertEquals(setOf("foo", "bar", "bazz", "qux"), trie1.lookupValues(parse("a{X c}")).toSet()) + assertEquals(setOf("foo", "bazz", "qux"), trie1.lookupValues(parse("a{c c}")).toSet()) + + val trie2 = trie1.remove(t2, "bar") + val trie3 = trie2.put(t2, "blah") + + assertEquals(setOf("foo", "qux", "blah"), trie3.lookupValues(parse("a{b X}")).toSet()) + assertEquals(setOf("foo", "bazz", "qux", "blah"), trie3.lookupValues(parse("a{X c}")).toSet()) + } + + @Test + fun testRefTerm() { + val varRef = MockRef(MockVar("TAIL")) + val pattern = MockFun("g", MockFun("h"), MockFun("f", varRef, MockFun("nil"))) + val trie1 = TermTrie().runs( + { put(parse("g {h f {a nil}}"), "bar") }, + { put(parse("g {h f {a f {b nil}}}"), "bazz") }, + { put(pattern, "foo") }, + { put(parse("g {h foo {nil}}"), "qux") } + ) + + assertEquals(listOf("qux"), trie1.lookupValues(parse("g {h foo {nil}}")).toList()) + + val key = MockFun("g", MockFun("h"), parse("f {c nil}")) + assertEquals(listOf("foo"), trie1.lookupValues(key).toList()) + } + + @Test + fun testRefList() { + val trie = TermTrie().runs( + { put(parse("f{X Y}"), "foo") }, + { put(parse("f{a Z}"), "bar") }, + { put(parse("f{a f{b W}}"), "bazz") } + ) + + val f = parse("f {b nil}") + val key = MockFun("f", parse("a"), MockRef(MockRef(f))) + assertEquals(setOf("foo", "bar", "bazz"), trie.lookupValues(key).toSet()) + } + + + @Test + fun testCyclicTerm() { + val trie = TermTrie().runs( + { put(parse("cst{ @1 n{ a{b c} d{^1} } }"), "foo") }, + { put(parse("cst{ n{ @2 a{b ^2} d{e} } }"), "bar") } + ) + + assertEquals(setOf("foo", "bar"), trie.lookupValues(parse("cst{ N }")).toSet()) + assertEquals(setOf("foo", "bar"), trie.lookupValues(parse("cst{ n{ X Y } }")).toSet()) + assertEquals(setOf("foo", "bar"), trie.lookupValues(parse("cst{ n{ X d{Z} } }")).toSet()) + assertEquals(setOf("foo", "bar"), trie.lookupValues(parse("cst{ n{ a{b W} V } }")).toSet()) + assertEquals(listOf("bar"), trie.lookupValues(parse("cst{ n{ @1 a{b ^1} V } }"))) + assertEquals(listOf("foo"), trie.lookupValues(parse("cst{ @1 n{ a{b S} d{^1} } }"))) + + val trie2 = trie.runs( + { remove(parse("cst{ n{ @2 a{b ^2} d{e} } }"), "bar") } + ) + assertEquals(emptyList(), trie2.lookupValues(parse("cst{ n{ @1 a{b ^1} V } }"))) + assertEquals(listOf("foo"), trie2.lookupValues(parse("cst{ @1 n{ a{b S} d{^1} } }"))) + + } + + fun T.runs(vararg blocks: T.() -> T): T { + var t = this + for (blk in blocks) { + t = t.blk() + } + return t + } + +} \ No newline at end of file diff --git a/reactor/Test/test/jetbrains/mps/unification/test/AssertAll.java b/reactor/Test/test/jetbrains/mps/unification/test/AssertAll.java new file mode 100644 index 00000000..84cf034a --- /dev/null +++ b/reactor/Test/test/jetbrains/mps/unification/test/AssertAll.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification.test; + +import jetbrains.mps.unification.Term; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; + +/** + * @author fyodor on 09.07.2014. + */ +public class AssertAll { + + public static void assertEqualsAll(Collection parsed, Term... terms) { + assertEquals(parsed, Arrays.asList(terms)); + } + +} diff --git a/reactor/Test/test/jetbrains/mps/unification/test/AssertStructurallyEquivalent.java b/reactor/Test/test/jetbrains/mps/unification/test/AssertStructurallyEquivalent.java new file mode 100644 index 00000000..06887b18 --- /dev/null +++ b/reactor/Test/test/jetbrains/mps/unification/test/AssertStructurallyEquivalent.java @@ -0,0 +1,201 @@ +/* + * Copyright 2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification.test; + +import jetbrains.mps.unification.Term; +import static org.junit.Assert.*; + +import java.util.*; + +/** + * @author Fedor Isakov + */ +public class AssertStructurallyEquivalent { + + public static void assertEquivalent(Term a, Term b) throws Exception { + + final Signature signature = new Signature(); + signature.setWalkers( + // first pass + new TermWalker( + new TermVisitor(Term.Kind.FUN) { + @Override + public Collection visit(Term term) throws Exception { + signature.label(term); + return term.arguments(); + } + } + , + new TermVisitor(Term.Kind.REF) { + @Override + public Collection visit(Term ref) throws Exception { + if (ref.get().is(Term.Kind.FUN)) { + return Collections.singletonList(ref.get()); + } + return Collections.emptyList(); + } + } + ), + // second pass + new TermWalker( + new TermVisitor(Term.Kind.FUN) { + @Override + public Collection visit(Term term) throws Exception { + signature.appendSignature("@").append(signature.getLabel(term)).append(term.symbol()); + return term.arguments(); + } + }, + new TermVisitor(Term.Kind.VAR) { + @Override + public Collection visit(Term var) throws Exception { + signature.appendSignature("$").append(var.symbol()); + return Collections.emptyList(); + } + }, new TermVisitor(Term.Kind.REF) { + @Override + public Collection visit(Term ref) throws Exception { + if (ref.get().is(Term.Kind.FUN)) { + Integer label = signature.getLabel(ref.get()); + assertNotNull("not found label for '" + ref.get() + "'", label); + if (signature.isTopLevel(ref.get())) { + signature.appendSignature("^").append(label); + return Collections.emptyList(); + } + else { + return Collections.singletonList(ref.get()); + } + } + else if (ref.get().is(Term.Kind.VAR)) { + signature.appendSignature("^").append(ref.get().symbol()); + return Collections.emptyList(); + } + else { + throw new UnsupportedOperationException(); + } + + } + }) + ); + + String signa = signature.getSignature(a); + String signb = signature.getSignature(b); + + assertEquals(signa, signb); + } + + + private static class Signature { + + private TermWalker[] walkers; + + private IdentityHashMap labels = new IdentityHashMap(); + private int label = 1; + private StringBuilder signature = new StringBuilder(); + + protected void label(Term term) { + labels.put(term, label++); + } + + protected Integer getLabel(Term term) { + return labels.get(term); + } + + protected boolean isTopLevel(Term term) { return labels.get(term) == 1; } + + protected StringBuilder appendSignature(String str) { + return signature.append(str); + } + + public String getSignature (Term term) throws Exception { + reset(); + for (TermWalker walker : walkers) { + walker.walk(term); + } + return signature.toString(); + } + + protected void reset() { + labels.clear(); + this.label = 1; + signature.setLength(0); + } + + protected void setWalkers(TermWalker... walkers) { + this.walkers = walkers; + } + } + + private static abstract class TermVisitor { + + private Term.Kind kind; + + public TermVisitor(Term.Kind kind) { + this.kind = kind; + } + + public Term.Kind applicableTo() { + return kind; + } + + + + public abstract Collection visit(T t) throws Exception ; + + } + + private static class TermWalker { + + private static Object SINGLETON = new Object(); + + private Map> visitorMap = new HashMap>(); + + public TermWalker(TermVisitor... visitors) { + for (TermVisitor visitor : visitors) { + visitorMap.put(visitor.applicableTo(), visitor); + } + } + + public void walk(Term term) throws Exception { + walk(term, new IdentityHashMap()); + } + + private void walk(Term term, Map visited) throws Exception { + if (term.is(Term.Kind.FUN)) { + visited.put(term, SINGLETON); + } + Collection arguments = switchClass(term); + for (Term arg : arguments) { + if (visited.containsKey(arg)) { + continue; + } + walk(arg, visited); + } + } + + private Collection switchClass(Term term) throws Exception { + for (Map.Entry> e : visitorMap.entrySet()) { + if (term.is(e.getKey())) { + TermVisitor value = (TermVisitor) e.getValue(); + return value.visit(term); + } + } + return Collections.emptyList(); + } + + } + +} diff --git a/reactor/Test/test/jetbrains/mps/unification/test/AssertUnification.java b/reactor/Test/test/jetbrains/mps/unification/test/AssertUnification.java new file mode 100644 index 00000000..450ea07b --- /dev/null +++ b/reactor/Test/test/jetbrains/mps/unification/test/AssertUnification.java @@ -0,0 +1,157 @@ +/* + * Copyright 2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification.test; + +import jetbrains.mps.unification.Substitution; +import static jetbrains.mps.unification.Substitution.*; + +import jetbrains.mps.unification.TermWrapper; +import jetbrains.mps.unification.Unification; +import jetbrains.mps.unification.Term; + +import java.util.*; + +import static jetbrains.mps.unification.test.AssertStructurallyEquivalent.assertEquivalent; +import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; + + +/** + * Created by fyodor on 09.06.2014. + */ +public class AssertUnification { + + public static final Comparator BINDING_COMPARATOR = new Comparator() { + @Override + public int compare(Binding a, Binding b) { + return a.var().compareTo(b.var()); + } + }; + + public static Binding bind(Term v, Term n) { + return new Binding(v, n); + } + + public static void assertSameBindings(Collection expected, Collection actual) throws Exception { + ArrayList expectedCopy = new ArrayList(expected); + Collections.sort(expectedCopy, BINDING_COMPARATOR); + Iterator expIt = expectedCopy.iterator(); + ArrayList actualCopy = new ArrayList(actual); + Collections.sort(actualCopy, BINDING_COMPARATOR); + Iterator actIt = actualCopy.iterator(); + + while(expIt.hasNext() && actIt.hasNext()) { + Binding expb = expIt.next(); + Binding actb = actIt.next(); + + assertEquals(expb.var(), actb.var()); + assertEquivalent(expb.term(), actb.term()); + } + if(expIt.hasNext() || actIt.hasNext()) throw new Exception("mismatched number of bindings"); + } + + public static void assertUnifiesWithBindings(Term s, Term t, Substitution.Binding ... bindings) throws Exception{ + Substitution subs = Unification.INSTANCE.unify(s, t); + + assertTrue(subs.isSuccessful()); + assertSameBindings( + Arrays.asList( + bindings + ), + subs.bindings()); + + Substitution subs2 = Unification.INSTANCE.unify(t, s); + + assertTrue(subs2.isSuccessful()); + assertSameBindings(subs.bindings(), subs2.bindings()); + } + + public static void assertUnifiesWithBindings(Term s, Term t, TermWrapper wrapper, Substitution.Binding ... bindings) throws Exception{ + Substitution subs = Unification.INSTANCE.unify(s, t, wrapper); + + assertTrue(subs.isSuccessful()); + assertSameBindings( + Arrays.asList( + bindings + ), + subs.bindings()); + + Substitution subs2 = Unification.INSTANCE.unify(t, s, wrapper); + + assertTrue(subs2.isSuccessful()); + assertSameBindings(subs.bindings(), subs2.bindings()); + } + + public static void assertUnifiesWithBindingsAsymm(Term s, Term t, Substitution.Binding ... bindings) throws Exception{ + Substitution subs = Unification.INSTANCE.unify(s, t); + + assertTrue(subs.isSuccessful()); + assertSameBindings( + Arrays.asList( + bindings + ), + subs.bindings()); + } + + public static void assertUnificationFails(Term s, Term t) throws Exception { + Substitution subs1 = Unification.INSTANCE.unify(s, t); + + assertFalse(subs1.isSuccessful()); + + Substitution subs2 = Unification.INSTANCE.unify(s, t); + + assertFalse(subs2.isSuccessful()); + } + + public static void assertUnificationFails(Term s, Term t, TermWrapper wrapper) throws Exception { + Substitution subs1 = Unification.INSTANCE.unify(s, t, wrapper); + + assertFalse(subs1.isSuccessful()); + + Substitution subs2 = Unification.INSTANCE.unify(s, t, wrapper); + + assertFalse(subs2.isSuccessful()); + } + + public static void assertUnificationFails(Term s, Term t, FailureCause failureCause) throws Exception { + Substitution subs1 = Unification.INSTANCE.unify(s, t); + + assertFalse(subs1.isSuccessful()); + assertSame(failureCause, subs1.failureCause()); + + Substitution subs2 = Unification.INSTANCE.unify(t, s); + + assertFalse(subs2.isSuccessful()); + assertSame(failureCause, subs2.failureCause()); + } + + public static void assertUnificationFails(Term s, Term t, FailureCause failureCause, Object... details) throws Exception { + Substitution subs1 = Unification.INSTANCE.unify(s, t); + + assertFalse(subs1.isSuccessful()); + assertSame(failureCause, subs1.failureCause()); + assertArrayEquals(details, subs1.failureDetails()); + + Substitution subs2 = Unification.INSTANCE.unify(t, s); + + assertFalse(subs2.isSuccessful()); + assertSame(failureCause, subs1.failureCause()); + // dont test for details: may be in different order + } + +} + diff --git a/reactor/Test/test/jetbrains/mps/unification/test/ParserTests.java b/reactor/Test/test/jetbrains/mps/unification/test/ParserTests.java new file mode 100644 index 00000000..8c615022 --- /dev/null +++ b/reactor/Test/test/jetbrains/mps/unification/test/ParserTests.java @@ -0,0 +1,198 @@ +/* + * Copyright 2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification.test; + +import jetbrains.mps.unification.Term; +import org.junit.ComparisonFailure; +import org.junit.Test; + +import static org.junit.Assert.*; + +import static jetbrains.mps.unification.test.MockTerm.*; +import static jetbrains.mps.unification.test.MockTermsParser.*; +import static jetbrains.mps.unification.test.AssertAll.*; +import static jetbrains.mps.unification.test.AssertStructurallyEquivalent.*; + +/** + * Created by fyodor on 10.06.2014. + */ +public class ParserTests { + + private static class LazyTermLookup implements TermLookup{ + private Term term; + + @Override + public Term lookupTerm() { + return term; + } + } + + @Test + public void testSingle() { + assertEquals(parse("a"), term("a")); + assertEquals(parseTerm("a").symbol(), term("a").symbol()); + assertEquals(parseVar("X").symbol(), var("X").symbol()); + assertEquals(parse("X"), var("X")); + assertEquals(parse("a{b}"), term("a", term("b"))); + assertEquals(parseTerm("a{b}").symbol(), term("a", term("b")).symbol()); + assertEquals(parse("a{b c}"), term("a", term("b"), term("c"))); + assertEquals(parse("a{b X}"), term("a", term("b"), var("X"))); + assertEquals(parse("a{X b}"), term("a", var("X"), term("b"))); + assertEquals(parse("a{X Y}"), term("a", var("X"), var("Y"))); + } + + @Test + public void testMuti() { + assertEqualsAll(parseAll("a b"), term("a"), term("b")); + assertEqualsAll(parseAll("X Y"), var("X"), var("Y")); + assertEqualsAll(parseAll("a{b} a{b}"), term("a", term("b")), term("a", term("b"))); + } + + @Test + public void testWhitespace() { + assertEquals(parse(" a"), term("a")); + assertEquals(parse(" Y "), var("Y")); + assertEquals(parse("a{ b }"), term("a", term("b"))); + assertEquals(parse("a{ b c }"), term("a", term("b"), term("c"))); + assertEquals(parse("a{b X} "), term("a", term("b"), var("X"))); + assertEquals(parse("a {X b}"), term("a", var("X"), term("b"))); + assertEquals(parse(" a{X Y } "), term("a", var("X"), var("Y"))); + } + + @Test + public void testDeep() { + assertEquals(parse("a{b{c{d{e f g}}}}"), + term("a", + term("b", + term("c", + term("d", + term("e"), term("f"), term("g")))))); + + assertEquals(parse("a{X b{c{d{Z e W f g} Y}}}"), + term("a", + var("X"), term("b", + term("c", + term("d", + var("Z"), term("e"), var("W"), term("f"), term("g")), + var("Y"))))); + } + + @Test + public void testRef() throws Exception { + LazyTermLookup termLookup = new LazyTermLookup(); + Term a = termLookup.term = term("a", ref(termLookup)); + assertEquivalent( + parse("@1a{^1}"), + a); + + Term b = term("b"); + assertEquivalent( + parse("a{@1b ^1}"), + term("a", b, ref(b))); + + Term c = term("c"); + assertEquivalent( + parse("a{^1 @1c}"), + term("a", ref(c), c)); + + Term b1 = term("b"); + Term b2 = term("b"); + assertEquivalent( + parse("a{@2b ^1 ^2 @1b}"), + term("a", b2, ref(b1), ref(b2), b1)); + } + + @Test + public void testVarRef() throws Exception { + Term x = var("X"); + + assertEquivalent( + parse("^X"), + ref(x)); + assertEquivalent( + parse("a{^X}"), + term("a", ref(x))); + + } + + @Test(expected = ComparisonFailure.class) + public void testNotEquivalent1() throws Exception { + Term d = term("d"); + assertEquivalent(parse("a{^1 @1c}"), + term("a", ref(d), d)); + } + + @Test(expected = MockTermsParser.ParseException.class) + public void testUnclosedFail() { + parse("a{b "); + } + + @Test(expected = MockTermsParser.ParseException.class) + public void testUnclosedFail2() { + parse("a{X b"); + } + + @Test(expected = MockTermsParser.ParseException.class) + public void testUnclosedFail3() { + parse("a{{X b}"); + } + + @Test(expected = MockTermsParser.ParseException.class) + public void testDoubleClosed() { + parse("a{X b}}"); + } + + @Test(expected = IllegalArgumentException.class) + public void testFailMulti1() { + parse(" a Y"); + } + + @Test(expected = IllegalArgumentException.class) + public void testFailMulti2() { + parse("a b"); + } + + @Test(expected = MockTermsParser.ParseException.class) + public void testEmptyFail() { + parse(""); + } + + @Test(expected = MockTermsParser.ParseException.class) + public void testEmptyChildrenFail() { + parse("a{}"); + } + + @Test(expected = MockTermsParser.ParseException.class) + public void testWrongStartFail() { + parse("{a}"); + } + + @Test(expected = MockTermsParser.ParseException.class) + public void testVarHasChilrenFail() { + parse("X{a}"); + } + + @Test(expected = MockTermsParser.ParseException.class) + public void testExtraSymbolFail() { + parse("a}"); + } + + @Test(expected = MockTermsParser.ParseException.class) + public void testNonExistingRefFail() { + parse("a{b ^1}"); + } +} diff --git a/reactor/Test/test/jetbrains/mps/unification/test/SolverTests.java b/reactor/Test/test/jetbrains/mps/unification/test/SolverTests.java new file mode 100644 index 00000000..db74a08d --- /dev/null +++ b/reactor/Test/test/jetbrains/mps/unification/test/SolverTests.java @@ -0,0 +1,548 @@ +/* + * Copyright 2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jetbrains.mps.unification.test; + +import jetbrains.mps.unification.Term; +import jetbrains.mps.unification.TermWrapper; +import org.jetbrains.annotations.NotNull; +import org.junit.Ignore; +import org.junit.Test; +import org.omg.CORBA.UNKNOWN; + +import java.util.Collection; +import java.util.Collections; + +import static jetbrains.mps.unification.Substitution.FailureCause.*; +import static jetbrains.mps.unification.test.AssertUnification.*; +import static jetbrains.mps.unification.test.MockTerm.*; +import static jetbrains.mps.unification.test.MockTermsParser.*; + +/** + * Created by fyodor on 09.06.2014. + */ +public class SolverTests { + + @Test + public void test1() throws Exception { + assertUnifiesWithBindings( + term("a"), + var("X"), + + bind(var("X"), term("a")) + ); + assertUnifiesWithBindings( + var("Y"), + var("X"), + + bind(var("X"), var("Y")) + ); + assertUnifiesWithBindings( + parse("a{Y}"), + parse("a{X}"), + + bind(var("X"), var("Y")) + ); + } + + @Test + public void test2() throws Exception { + assertUnifiesWithBindings( + parse("a{b c}"), + parse("a{X Y}"), + + bind(var("X"), term("b")), + bind(var("Y"), term("c")) + ); + assertUnifiesWithBindings( + parse("a{b Y}"), + parse("a{X c}"), + + bind(var("X"), term("b")), + bind(var("Y"), term("c")) + ); + assertUnifiesWithBindings( + parse("a{b{c} X Y Z}"), + parse("a{X Y Z b{c}}"), + + bind(var("X"), parse("b{c}")), + bind(var("Y"), parse("b{c}")), + bind(var("Z"), parse("b{c}")) + ); + assertUnifiesWithBindings( + parse("a{b{c} Z Y X}"), + parse("a{Z Y X b{W}}"), + + bind(var("X"), parse("b{c}")), + bind(var("Y"), parse("b{c}")), + bind(var("Z"), parse("b{c}")), + bind(var("W"), parse("c")) + ); + } + + @Test + public void test3() throws Exception { + assertUnifiesWithBindings( + parse("a{b{X} c{Y}}"), + parse("a{V W}"), + + bind(var("V"), parseTerm("b{X}")), + bind(var("W"), parseTerm("c{Y}")) + ); + assertUnifiesWithBindings( + parse("a{X}"), + parse("a{Y}"), + + bind(var("X"), var("Y")) + ); + assertUnifiesWithBindings( + parse("a{b{X} c{Y}}"), + parse("a{b{V} c{W}}"), + + bind(var("V"), var("X")), + bind(var("W"), var("Y")) + ); + } + + @Test + public void test4() throws Exception { + assertUnifiesWithBindings( + parse("a{b{X} c{Y}}"), + parse("a{Y Z}"), + + bind(var("Y"), parseTerm("b{X}")), + bind(var("Z"), parseTerm("c{Y}")) + ); + } + + @Test + public void test5() throws Exception { + assertUnifiesWithBindings( + parseTerm("a{b{c} Y}"), + parseTerm("a{Y b{X}}"), + + bind(var("Y"), parseTerm("b{c}")), + bind(var("X"), term("c")) + ); + } + + @Test + public void test6() throws Exception { + assertUnifiesWithBindings( + parseTerm("a{b{X} c{Y}}"), + parseTerm("a{Y c{b{d}}}"), + + bind(var("Y"), parseTerm("b{X}")), + bind(var("X"), term("d")) + ); + } + + @Test + public void test7() throws Exception { + assertUnifiesWithBindings( + parseTerm("a{b{X} c{Y} Z}"), + parseTerm("a{Y c{b{d{Z}}} e{f}}"), + + bind(var("X"), parseTerm("d{Z}")), + bind(var("Y"), parseTerm("b{X}")), + bind(var("Z"), parseTerm("e{f}")) + ); + } + + @Test + public void test8() throws Exception { + assertUnifiesWithBindings( + parseTerm("a{b{c d} e{f} }"), + parseTerm("a{b{X Y} e{Z} }"), + + bind(var("X"), parseTerm("c")), + bind(var("Y"), parseTerm("d")), + bind(var("Z"), parseTerm("f")) + ); + } + + @Test + public void test9() throws Exception { + assertUnifiesWithBindings( + parseTerm("a{b{c d} e{X} }"), + parseTerm("a{b{X Y} e{Z} }"), + + bind(var("X"), parseTerm("c")), + bind(var("Y"), parseTerm("d")), + bind(var("Z"), parseTerm("c")) + ); + } + + @Test + public void test10() throws Exception { + assertUnifiesWithBindings( + parseTerm("a{b{c Y} e{X} }"), + parseTerm("a{X e{b{Z d}} }"), + + bind(var("X"), parseTerm("b{c Y}")), + bind(var("Y"), parseTerm("d")), + bind(var("Z"), parseTerm("c")) + ); + } + + @Test + public void test11() throws Exception { + assertUnifiesWithBindings( + parseTerm("a{b{c d} e{f g{b{c d}}} }"), + parseTerm("a{X e{f g{X }} }"), + + bind(var("X"), parseTerm("b{c d}")) + ); + } + + @Test + public void test12() throws Exception { + assertUnifiesWithBindings( + parse("node{name{foo} child{X}}"), + parse("node{name{foo} child{Y}}"), + + bind(var("X"), var("Y")) + ); + } + + @Test + public void test13() throws Exception { + assertUnifiesWithBindings( + parse("node{name{foo} child{node{name{bar}}}}"), + parse("node{name{foo} child{X}}"), + + bind(var("X"), parseTerm("node{name{bar}}")) + ); + } + + @Test + public void test14() throws Exception { + assertUnifiesWithBindings( + parse("f{X g{a}}"), + parse("f{g{Y} g{Y}}"), + + bind(var("X"), parseTerm("g{Y}")), + bind(var("Y"), parseTerm("a")) + ); + } + + @Test + public void test15() throws Exception { + assertUnifiesWithBindingsAsymm( + parse("h{X1 f{Y0 Y0} Y1}"), + parse("h{f{X0 X0} Y1 X1}"), + + bind(var("X0"), parseVar("Y0")), + bind(var("X1"), parseTerm("f{Y0 Y0}")), + bind(var("Y1"), parseTerm("f{Y0 Y0}")) + ); + } + + @Test + public void test16() throws Exception { + // this test illustrates why the used algorithm is superior to recursive descent: + // the latter would have an exponential complexity because of "functional" form of + // substitution instead of the "triangular" form used here. + + assertUnifiesWithBindingsAsymm( + parse("h{X1 X2 X3 X4 X5 X6 X7 X8 X9 " + + "f{Y0 Y0} f{Y1 Y1} f{Y2 Y2} f{Y3 Y3} f{Y4 Y4} f{Y5 Y5} f{Y6 Y6} f{Y7 Y7} f{Y8 Y8} Y9}"), + parse("h{f{X0 X0} f{X1 X1} f{X2 X2} f{X3 X3} f{X4 X4} f{X5 X5} f{X6 X6} f{X7 X7} f{X8 X8} " + + "Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 Y9 X9}"), + + bind(var("X0"), parseVar("Y0")), + bind(var("X1"), parseTerm("f{Y0 Y0}")), + bind(var("X2"), parseTerm("f{Y1 Y1}")), + bind(var("X3"), parseTerm("f{Y2 Y2}")), + bind(var("X4"), parseTerm("f{Y3 Y3}")), + bind(var("X5"), parseTerm("f{Y4 Y4}")), + bind(var("X6"), parseTerm("f{Y5 Y5}")), + bind(var("X7"), parseTerm("f{Y6 Y6}")), + bind(var("X8"), parseTerm("f{Y7 Y7}")), + bind(var("X9"), parseTerm("f{Y8 Y8}")), + bind(var("Y1"), parseTerm("f{Y0 Y0}")), + bind(var("Y2"), parseTerm("f{Y1 Y1}")), + bind(var("Y3"), parseTerm("f{Y2 Y2}")), + bind(var("Y4"), parseTerm("f{Y3 Y3}")), + bind(var("Y5"), parseTerm("f{Y4 Y4}")), + bind(var("Y6"), parseTerm("f{Y5 Y5}")), + bind(var("Y7"), parseTerm("f{Y6 Y6}")), + bind(var("Y8"), parseTerm("f{Y7 Y7}")), + bind(var("Y9"), parseTerm("f{Y8 Y8}")) + ); + } + + @Test + public void testCyclicVar() throws Exception { + assertUnifiesWithBindings( + parse("@1 a{b ^1}"), + parse("X"), + + bind(var("X"), parse("@1 a{b ^1}")) + ); + assertUnifiesWithBindings( + parse("@1 a{^1 b c}"), + parse("X"), + + bind(var("X"), parse("@1 a{^1 b c}")) + ); + assertUnifiesWithBindings( + parse("a{X c{X} }"), + parse("a{b{Y} c{@1 b{@2 c{b{^2}}}}}"), + + bind(var("X"), parse("b{Y}")), + bind(var("Y"), parse("@1 c{b{^1}}")) + ); + } + + @Test + public void testVarRef() throws Exception { + assertUnifiesWithBindings( + parse("^X"), + parse("Y"), + + bind(var("X"), var("Y")) + ); + assertUnifiesWithBindings( + parse("a{b ^X}"), + parse("a{b c{d}}"), + + bind(var("X"), parse("c{d}")) + ); + assertUnifiesWithBindings( + parse("a{b c{X} ^X}"), + parse("a{b c{d} d}"), + + bind(var("X"), parse("d")) + ); + assertUnifiesWithBindings( + parse("a{b{d} c{X} ^X}"), + parse("a{b{^X} c{d} d}"), + + bind(var("X"), parse("d")) + ); + assertUnifiesWithBindings( + parse("a{b{d} c{X}}"), + parse("a{b{^X} c{d}}"), + + bind(var("X"), parse("d")) + ); + } + + @Test + public void testUnifyExternalRef() throws Exception { + Term termRef = ref(parse("f {a f{b f{c d}}}")); + Term varRef = ref(var("TAIL")); + + Term a = term("g", termRef); + Term b = term("g", term("f", var("HEAD"), varRef)); + + assertUnifiesWithBindings( + a, + b, + + bind(var("HEAD"), parse("a")), + bind(var("TAIL"), parse("f{b f{c d}}")) + ); + } + + @Test + public void testUnifyExternalRef2() throws Exception { + Term list = parse("f {a f{b f{c d}}}"); + Term termRef = ref(list); + Term varRef = ref(var("TAIL")); + + + Term a = term("g", termRef); + Term aa = term("h", a, list); + Term b = term("g", term("f", var("HEAD"), varRef)); + Term bb = term("h", b, var("LIST")); + + assertUnifiesWithBindings( + aa, + bb, + + bind(var("LIST"), parse("f {a f{b f{c d}}}")), + bind(var("HEAD"), parse("a")), + bind(var("TAIL"), parse("f{b f{c d}}")) + ); + } + + @Test + public void testUnifyExternalRef3() throws Exception { + Term empty = parse("f {nil}"); + Term varRef = ref(var("TAIL")); + + Term aa = term("g", empty); + Term bb = term("g", term("f", varRef)); + + assertUnifiesWithBindings( + aa, + bb, + + bind(var("TAIL"), parse("nil")) + ); + } + + @Test + public void testWrapper() throws Exception { + Term t1 = parse("a{b c{X}}"); + Term t2 = parse("a{X c{Y}}"); + Term p1 = parse("a{META c{d}}"); + Term p2 = parse("a{b c{META}}"); + + class Wrapper implements Term { + Term wrapped; + + Wrapper(Term term) { this.wrapped = term; } + @Override public Object symbol() { return wrapped; } + @Override public Collection arguments() { return Collections.emptyList(); } + @Override public Term get() { return this; } + @Override public boolean is(Kind kind) { return Kind.FUN == kind; } + @Override public int compareTo(@NotNull Term other) { + return String.valueOf(symbol()).compareTo(String.valueOf(other.symbol())); + } + + } + + TermWrapper wrapper = new TermWrapper() { + @Override + public Term wrap(Term orig) { + return (orig.is(Kind.VAR) && "META".equals(orig.symbol())) ? new Wrapper(orig) : orig; + } + + @Override + public Term unwrap(Term maybeWrapper) { + return maybeWrapper instanceof Wrapper ? ((Wrapper)maybeWrapper).wrapped : maybeWrapper; + } + }; + + assertUnifiesWithBindings(t1, p1, + bind(var("META"), parse("b")), + bind(var("X"), parse("d")) + ); + assertUnificationFails(t1, p1, wrapper); + + assertUnifiesWithBindings(t1, p2, wrapper, + bind(var("X"), parse("META")) + ); + + assertUnifiesWithBindings(t2, p2, wrapper, + bind(var("X"), parse("b")), + bind(var("Y"), parse("META")) + ); + } + + @Test + public void testFailConflict() throws Exception { + assertUnificationFails( + term("a"), + term("b"), + + SYMBOL_CLASH, + "a", + "b" + ); + assertUnificationFails( + parse("node{name{X} child{abc}}"), + parse("node{name{foo} child{X}}"), + + SYMBOL_CLASH, + "abc", + "foo" + ); + } + + @Test + public void testFailCard() throws Exception { + assertUnificationFails( + parse("a{b c}"), + parse("a{X}") + ); + } + + @Test + public void testFailCyclic() throws Exception { + assertUnificationFails( + term("g", ref(parse("f {h X}"))), + term("g", var("X")), + + CYCLE_DETECTED + ); + assertUnificationFails( + parse("t {@1 f {h X} g {f {h X}}}"), + parse("t {Y g {X}}"), + + CYCLE_DETECTED + ); + assertUnificationFails( + parse("a{b X}"), + parse("X"), + + CYCLE_DETECTED + ); + assertUnificationFails( + parse("f{X}"), + parse("X"), + + CYCLE_DETECTED + ); + assertUnificationFails( + parse("X"), + parse("f{X}"), + + CYCLE_DETECTED + ); + assertUnificationFails( + parse("f{f{X}}"), + parse("f{X}"), + + CYCLE_DETECTED + ); + assertUnificationFails( + parse("f{a{X} Y }"), + parse("f{Y a{b{X}}}"), + + CYCLE_DETECTED + ); + assertUnificationFails( + parse("a{X c{X}}"), + parse("a{b{Y} Y }"), + + CYCLE_DETECTED + ); + assertUnificationFails( + parse("a{ b{c{b{c{b{c{^2}}}}}} @2 b{c{b{c{^2}}}}}"), + parse("a{ b{Y} b{Y} }"), + + CYCLE_DETECTED + ); + assertUnificationFails( + parse("t {@1 f {h X} g {f {h X}}}"), + parse("t {Y g {X}}"), + + CYCLE_DETECTED + ); + assertUnificationFails( + parse("t {@1 f {h X} g {^1}}"), + parse("t {Y g {X}}"), + + CYCLE_DETECTED + ); + } + + +} diff --git a/reactor/pom.xml b/reactor/pom.xml new file mode 100644 index 00000000..0c023d74 --- /dev/null +++ b/reactor/pom.xml @@ -0,0 +1,110 @@ + + + 4.0.0 + + + + UTF-8 + 1.2.30 + 0.7 + + + jetbrains.mps.logic.conreactor + conreactor-main + 0.9 + pom + + + + org.jetbrains.kotlin + kotlin-runtime + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + com.github.andrewoma.dexx + collection + ${collection.version} + + + com.intellij + annotations + 12.0 + + + net.sf.trove4j + trove4j + 3.0.3 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + attach-sources + + jar + + + + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + 1.8 + + + + compile + compile + compile + + + + test-compile + test-compile + test-compile + + + + + + + + + API + Core + Test + + + +