├── .gitignore ├── .plan ├── LICENSE ├── README.md ├── build.sbt ├── docs └── images │ └── example_graph.png ├── engine-core └── src │ ├── main │ └── scala │ │ └── org │ │ └── scalarules │ │ ├── derivations │ │ ├── DerivationGraph.scala │ │ ├── DerivationTools.scala │ │ └── derivations.scala │ │ ├── engine │ │ ├── Conditions.scala │ │ ├── FactEngine.scala │ │ ├── LRUCache.scala │ │ ├── evaluations.scala │ │ ├── metadata.scala │ │ └── package.scala │ │ ├── facts │ │ └── facts.scala │ │ ├── services │ │ └── HeuristicService.scala │ │ └── utils │ │ ├── FactMacros.scala │ │ ├── PrettyPrinter.scala │ │ └── SourcePosition.scala │ └── test │ └── scala │ └── org │ └── scalarules │ └── engine │ ├── FactEngineTest.scala │ ├── FactEngineTestGlossary.scala │ ├── FactTest.scala │ ├── LRUCacheTest.scala │ └── SubRunDerivationTest.scala ├── engine-test-utils └── src │ ├── main │ └── scala │ │ └── org │ │ └── scalarules │ │ └── testutils │ │ ├── TestUtils.scala │ │ └── nl │ │ ├── HeuristicTester.scala │ │ └── TestDsl.scala │ └── test │ └── scala │ └── org │ └── scalarules │ └── testutils │ └── nl │ ├── TestDslTest.scala │ └── TestDslTestBerekening.scala ├── engine └── src │ ├── main │ └── scala │ │ └── org │ │ └── scalarules │ │ ├── dsl │ │ ├── core │ │ │ ├── glossaries │ │ │ │ └── Glossary.scala │ │ │ ├── operators │ │ │ │ ├── Addable.scala │ │ │ │ ├── BinaryOperable.scala │ │ │ │ ├── Divisible.scala │ │ │ │ ├── Multipliable.scala │ │ │ │ └── Subtractable.scala │ │ │ ├── projections │ │ │ │ └── ProjectableFields.scala │ │ │ ├── temporal │ │ │ │ └── LocalDate.scala │ │ │ └── types │ │ │ │ ├── AddableValues.scala │ │ │ │ ├── DivisibleValues.scala │ │ │ │ ├── MultipliableValues.scala │ │ │ │ └── SubtractableValues.scala │ │ └── nl │ │ │ ├── ScalaRulesDsl.scala │ │ │ ├── datum │ │ │ ├── DatumImplicits.scala │ │ │ └── package.scala │ │ │ └── grammar │ │ │ ├── AfrondingsWordsTrait.scala │ │ │ ├── BerekeningFlow.scala │ │ │ ├── DslCondition.scala │ │ │ ├── DslConditionPart.scala │ │ │ ├── DslEvaluation.scala │ │ │ ├── DslGenericListAggregator.scala │ │ │ ├── DslListAggregators.scala │ │ │ ├── DslListFilter.scala │ │ │ ├── DslOrderedListAggregator.scala │ │ │ ├── DslTableSelector.scala │ │ │ ├── TestMacros.sc │ │ │ ├── berekeningen.scala │ │ │ ├── dslEvaluations.scala │ │ │ ├── meta │ │ │ ├── BerekeningReferentie.scala │ │ │ └── DslMacros.scala │ │ │ ├── operationEvaluations.scala │ │ │ └── package.scala │ │ ├── service │ │ └── dsl │ │ │ └── BusinessService.scala │ │ └── utils │ │ └── GlossaryConverter.scala │ └── test │ └── scala │ └── org │ └── scalarules │ ├── dsl │ ├── core │ │ ├── DivisibleValuesBerekening.scala │ │ ├── DivisibleValuesGlossary.scala │ │ ├── DivisibleValuesTest.scala │ │ ├── LijstBewerkingen.scala │ │ ├── LijstBewerkingenGlossary$.scala │ │ ├── LijstBewerkingenTest.scala │ │ ├── TableSelectorBerekening.scala │ │ ├── TableSelectorGlossary.scala │ │ ├── TableSelectorTest.scala │ │ └── projections │ │ │ ├── ProjectableFieldsCalculation.scala │ │ │ ├── ProjectableFieldsGlossary.scala │ │ │ └── ProjectableFieldsTest.scala │ └── nl │ │ ├── datum │ │ ├── DatumImplicitsTest.scala │ │ ├── DatumTest.scala │ │ ├── DatumTestGlossary.scala │ │ └── DatumTestsBerekening.scala │ │ └── grammar │ │ ├── AfrondingsTestBerekening.scala │ │ ├── AfrondingsTestBerekeningGlossary.scala │ │ ├── AfrondingsWordsTraitTest.scala │ │ ├── ConditionsBerekening.scala │ │ ├── ConditionsBerekeningGlossary.scala │ │ ├── ConditionsBerekeningTest.scala │ │ ├── ConditionsTest.scala │ │ ├── DslEvaluationTest.scala │ │ ├── DslListEvaluationTest.scala │ │ ├── LijstBerekeningGlossary.scala │ │ ├── LijstFilterBerekeningTest.scala │ │ ├── LijstFilterBerekeningen.scala │ │ ├── LoopBerekening.scala │ │ ├── LoopBerekeningGlossary.scala │ │ └── LoopBerekeningTest.scala │ ├── service │ └── dsl │ │ ├── BusinessServiceTest.scala │ │ ├── BusinessServiceTestBerekeningen.scala │ │ ├── BusinessServiceTestGlossaries.scala │ │ └── TestBusinessServices.scala │ └── utils │ ├── InternalHeuristicTester.scala │ ├── InternalTestDsl.scala │ ├── InternalTestDslTest.scala │ ├── InternalTestUtils.scala │ ├── MacroGlossaryTest.scala │ └── TestDslTestBerekening.scala └── project ├── build.properties ├── plugins.sbt ├── scalastyle-config.xml └── scalastyle-test-config.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/scala,intellij,eclipse,osx,windows 2 | 3 | ### Scala ### 4 | *.class 5 | *.log 6 | 7 | # sbt specific 8 | .cache 9 | .history 10 | .lib/ 11 | dist/* 12 | target/ 13 | lib_managed/ 14 | src_managed/ 15 | project/boot/ 16 | project/plugins/project/ 17 | 18 | # Scala-IDE specific 19 | .scala_dependencies 20 | .worksheet 21 | 22 | 23 | ### Intellij ### 24 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 25 | 26 | *.iml 27 | 28 | ## Directory-based project format: 29 | .idea 30 | # if you remove the above rule, at least ignore the following: 31 | 32 | # User-specific stuff: 33 | # .idea/workspace.xml 34 | # .idea/tasks.xml 35 | # .idea/dictionaries 36 | # .idea/shelf 37 | 38 | # Sensitive or high-churn files: 39 | # .idea/dataSources.ids 40 | # .idea/dataSources.xml 41 | # .idea/sqlDataSources.xml 42 | # .idea/dynamic.xml 43 | # .idea/uiDesigner.xml 44 | 45 | # Gradle: 46 | # .idea/gradle.xml 47 | # .idea/libraries 48 | 49 | # Mongo Explorer plugin: 50 | # .idea/mongoSettings.xml 51 | 52 | ## File-based project format: 53 | *.ipr 54 | *.iws 55 | 56 | ## Plugin-specific files: 57 | 58 | # IntelliJ 59 | /out/ 60 | 61 | # mpeltonen/sbt-idea plugin 62 | .idea_modules/ 63 | 64 | # JIRA plugin 65 | atlassian-ide-plugin.xml 66 | 67 | # Crashlytics plugin (for Android Studio and IntelliJ) 68 | com_crashlytics_export_strings.xml 69 | crashlytics.properties 70 | crashlytics-build.properties 71 | fabric.properties 72 | 73 | 74 | ### Eclipse ### 75 | *.pydevproject 76 | .metadata 77 | .gradle 78 | bin/ 79 | tmp/ 80 | *.tmp 81 | *.bak 82 | *.swp 83 | *~.nib 84 | local.properties 85 | .settings/ 86 | .loadpath 87 | 88 | # Eclipse Core 89 | .project 90 | 91 | # External tool builders 92 | .externalToolBuilders/ 93 | 94 | # Locally stored "Eclipse launch configurations" 95 | *.launch 96 | 97 | # CDT-specific 98 | .cproject 99 | 100 | # JDT-specific (Eclipse Java Development Tools) 101 | .classpath 102 | 103 | # Java annotation processor (APT) 104 | .factorypath 105 | 106 | # PDT-specific 107 | .buildpath 108 | 109 | # sbteclipse plugin 110 | .target 111 | 112 | # TeXlipse plugin 113 | .texlipse 114 | 115 | # STS (Spring Tool Suite) 116 | .springBeans 117 | 118 | 119 | ### OSX ### 120 | .DS_Store 121 | .AppleDouble 122 | .LSOverride 123 | 124 | # Icon must end with two \r 125 | Icon 126 | 127 | 128 | # Thumbnails 129 | ._* 130 | 131 | # Files that might appear in the root of a volume 132 | .DocumentRevisions-V100 133 | .fseventsd 134 | .Spotlight-V100 135 | .TemporaryItems 136 | .Trashes 137 | .VolumeIcon.icns 138 | 139 | # Directories potentially created on remote AFP share 140 | .AppleDB 141 | .AppleDesktop 142 | Network Trash Folder 143 | Temporary Items 144 | .apdisk 145 | 146 | 147 | ### Windows ### 148 | # Windows image file caches 149 | Thumbs.db 150 | ehthumbs.db 151 | 152 | # Folder config file 153 | Desktop.ini 154 | 155 | # Recycle Bin used on file shares 156 | $RECYCLE.BIN/ 157 | 158 | # Windows Installer files 159 | *.cab 160 | *.msi 161 | *.msm 162 | *.msp 163 | 164 | # Windows shortcuts 165 | *.lnk 166 | 167 | 168 | -------------------------------------------------------------------------------- /.plan: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Units: 5 | 6 | * Finance DSL (Bedrag, Per, etc) 7 | * Engine 8 | * Allows full programmatic usage 9 | * Includes Condition (DslCondtion's bare parts) 10 | * Includes operations with mathematical symbols 11 | * Derivation DSL 12 | * Core (actually included in Engine project, i.e. the Bare Items) 13 | * Language packs extend core domain objects an provide operations using natural language 14 | * NL 15 | * EN 16 | * ... 17 | 18 | 19 | * Quantity comes into our project (maybe duplicate in Finance) 20 | * Split into three projects 21 | * 22 | * Finance DSL Translate to English 23 | * Decouple Finance DSL from Engine (add Bridge package) 24 | 25 | 26 | org.scalarules.finance.core 27 | org.scalarules.finance.nl 28 | org.scalarules.finance.en 29 | 30 | org.scalarules.engine.FactEngine 31 | org.scalarules.engine.domain 32 | org.scalarules.engine.types 33 | org.scalarules.engine.operators 34 | 35 | org.scalarules.dsl.core 36 | org.scalarules.dsl.nl 37 | org.scalarules.dsl.en 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 scala-rules 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | // scalastyle:off 2 | 3 | 4 | // *** Settings *** 5 | 6 | useGpg := false 7 | 8 | lazy val commonSettings = Seq( 9 | organization := "org.scala-rules", 10 | organizationHomepage := Some(url("https://github.com/scala-rules")), 11 | homepage := Some(url("https://github.com/scala-rules/rule-engine")), 12 | version := "0.5.2-SNAPSHOT", 13 | scalaVersion := "2.11.8", 14 | scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature", "-Xlint", "-Xfatal-warnings") 15 | ) ++ staticAnalysisSettings ++ publishSettings 16 | 17 | 18 | // *** Projects *** 19 | 20 | lazy val ruleEngineRoot = (project in file(".")) 21 | .settings(commonSettings: _*) 22 | .settings( 23 | name := "scala-rules", 24 | description := "Scala Rules" 25 | ) 26 | .aggregate(engineCore, engine, engineTestUtils) 27 | 28 | lazy val engineCore = (project in file("engine-core")) 29 | .settings(commonSettings: _*) 30 | .settings( 31 | name := "rule-engine-core", 32 | description := "Rule Engine Core", 33 | libraryDependencies ++= engineCoreDependencies 34 | ) 35 | 36 | lazy val engine = (project in file("engine")) 37 | .settings(commonSettings: _*) 38 | .settings( 39 | name := "rule-engine", 40 | description := "Rule Engine", 41 | libraryDependencies ++= engineDependencies, 42 | addCompilerPlugin( 43 | "org.scalameta" % "paradise" % "3.0.0.95" cross CrossVersion.full ), 44 | scalacOptions += "-Xplugin-require:macroparadise", 45 | resolvers += Resolver.url( 46 | "scalameta-bintray", 47 | url("https://dl.bintray.com/scalameta/maven"))(Resolver.ivyStylePatterns) 48 | 49 | ) 50 | .dependsOn(engineCore) 51 | 52 | lazy val engineTestUtils = (project in file("engine-test-utils")) 53 | .settings(commonSettings: _*) 54 | .settings( 55 | name := "rule-engine-test-utils", 56 | description := "Rule Engine Test Utils", 57 | libraryDependencies ++= testUtilDependencies 58 | ) 59 | .dependsOn(engine) 60 | 61 | 62 | // *** Dependencies *** 63 | 64 | lazy val scalaTestVersion = "2.2.5" 65 | lazy val jodaTimeVersion = "2.4" 66 | lazy val jodaConvertVersion = "1.8" 67 | 68 | lazy val commonDependencies = Seq( 69 | "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.7.2", 70 | "com.fasterxml.jackson.jaxrs" % "jackson-jaxrs-json-provider" % "2.7.3", 71 | "joda-time" % "joda-time" % jodaTimeVersion, 72 | "org.joda" % "joda-convert" % jodaConvertVersion, 73 | "org.scalameta" %% "scalameta" % "1.2.0", 74 | "org.scala-rules" %% "finance-dsl" % "0.1.0", 75 | "org.scalatest" %% "scalatest" % scalaTestVersion % Test, 76 | "org.scalacheck" %% "scalacheck" % "1.12.5" % Test 77 | ) 78 | 79 | lazy val engineCoreDependencies = commonDependencies 80 | 81 | lazy val engineDependencies = commonDependencies 82 | 83 | lazy val testUtilDependencies = Seq( 84 | "org.scalatest" %% "scalatest" % scalaTestVersion 85 | ) ++ commonDependencies 86 | 87 | 88 | // *** Static analysis *** 89 | 90 | lazy val staticAnalysisSettings = { 91 | lazy val compileScalastyle = taskKey[Unit]("Runs Scalastyle on production code") 92 | lazy val testScalastyle = taskKey[Unit]("Runs Scalastyle on test code") 93 | 94 | Seq( 95 | scalastyleConfig in Compile := (baseDirectory in ThisBuild).value / "project" / "scalastyle-config.xml", 96 | scalastyleConfig in Test := (baseDirectory in ThisBuild).value / "project" / "scalastyle-test-config.xml", 97 | 98 | // The line below is needed until this issue is fixed: https://github.com/scalastyle/scalastyle-sbt-plugin/issues/44 99 | scalastyleConfig in scalastyle := (baseDirectory in ThisBuild).value / "project" / "scalastyle-test-config.xml", 100 | 101 | compileScalastyle := org.scalastyle.sbt.ScalastylePlugin.scalastyle.in(Compile).toTask("").value, 102 | testScalastyle := org.scalastyle.sbt.ScalastylePlugin.scalastyle.in(Test).toTask("").value 103 | ) 104 | } 105 | 106 | coverageExcludedPackages := ".*Macros" 107 | 108 | // Temporarily disabling scalastyle and coverage because they choke on scala meta macros 109 | // TODO : Extract the macros to their own subproject and disable these things there 110 | //addCommandAlias("verify", ";compileScalastyle;testScalastyle;coverage;test;coverageReport;coverageAggregate") 111 | addCommandAlias("verify", ";testScalastyle;test") 112 | addCommandAlias("release", ";clean;compile;publishSigned") 113 | 114 | 115 | // *** Publishing *** 116 | 117 | lazy val publishSettings = Seq( 118 | pomExtra := pom, 119 | publishMavenStyle := true, 120 | pomIncludeRepository := { _ => false }, 121 | licenses := Seq("MIT License" -> url("http://www.opensource.org/licenses/mit-license.php")), 122 | publishTo := { 123 | val nexus = "https://oss.sonatype.org/" 124 | if (isSnapshot.value) 125 | Some("snapshots" at nexus + "content/repositories/snapshots") 126 | else 127 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 128 | } 129 | ) 130 | 131 | lazy val pom = 132 | 133 | 134 | Vincent Zorge 135 | scala-rules@linuse.nl 136 | Linuse 137 | https://github.com/vzorge 138 | 139 | 140 | Jan-Hendrik Kuperus 141 | jan-hendrik@scala-rules.org 142 | Yoink Development 143 | http://www.yoink.nl 144 | 145 | 146 | Nathan Perdijk 147 | nathan@scala-rules.org 148 | 149 | 150 | 151 | scm:git:git@github.com:scala-rules/rule-engine.git 152 | scm:git:git@github.com:scala-rules/rule-engine.git 153 | git@github.com:scala-rules/rule-engine.git 154 | 155 | 156 | -------------------------------------------------------------------------------- /docs/images/example_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scala-rules/rule-engine/cbbb6315179049b3546499615f96252418446320/docs/images/example_graph.png -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/derivations/DerivationGraph.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.derivations 2 | 3 | trait DerivationGraph { 4 | 5 | } 6 | 7 | case class Node(derivation: Derivation, children: List[Node]) extends DerivationGraph 8 | 9 | -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/derivations/DerivationTools.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.derivations 2 | 3 | import DerivationTools._ 4 | import org.scalarules.engine._ 5 | import org.scalarules.facts.{Fact, OriginFact} 6 | 7 | import scala.annotation.tailrec 8 | 9 | object DerivationTools { 10 | 11 | /** 12 | * Constructs a Dependency graph for the provided list of Derivations. Each Derivation will yield a Node describing its output and other Nodes requiring its 13 | * output. 14 | * 15 | * @param derivations a List of all possible Derivations 16 | * @return a Set of Nodes which reference their dependent Nodes 17 | */ 18 | def constructGraph(derivations: List[Derivation]): Set[Node] = { 19 | constructNodes(derivations, computeAllInputs(derivations).map( (_, List()) ).toMap + (OriginFact -> List()), staleOutputsForDerivations(derivations).toList) 20 | } 21 | 22 | /** 23 | * Computes the Set of all unique Facts used by Derivations. 24 | * 25 | * @param derivations all available Derivations 26 | * @return a Set containing all unique input Facts 27 | */ 28 | def computeAllInputs(derivations: List[Derivation]): Set[Fact[Any]] = { 29 | def collectInputs(derivations: List[Derivation], acc: Set[Fact[Any]]) : Set[Fact[Any]] = derivations match { 30 | case d :: ds => collectInputs(ds, acc ++ d.input) 31 | case Nil => acc 32 | } 33 | collectInputs(derivations, Set()) 34 | } 35 | 36 | /** 37 | * Computes the Set of all unique Facts produced by Derivations. It also enforces uniqueness between Derivations, since we do not allow multiple Derivations 38 | * to produce the same Fact. 39 | * 40 | * @param derivations all available Derivations 41 | * @return a Set containing all unique output Facts 42 | */ 43 | def computeAllOutputs(derivations: List[Derivation]): Set[Fact[Any]] = { 44 | def collectOutputs(derivations: List[Derivation], acc: Set[Fact[Any]]) : Set[Fact[Any]] = derivations match { 45 | case d :: ds => if (acc contains d.output) { 46 | throw new IllegalStateException("Found a second derivation trying to satisfy output " + d.output) 47 | } else { 48 | collectOutputs(ds, acc + d.output) 49 | } 50 | case Nil => acc 51 | } 52 | collectOutputs(derivations, Set()) 53 | } 54 | 55 | private def resolveChildNodes(output: Fact[Any], nodesByInput: Map[Fact[Any], List[Node]]): List[Node] = if (nodesByInput contains output) nodesByInput(output) else List() 56 | private def constructNode(derivation: Derivation, nodesByInput: Map[Fact[Any], List[Node]]): Node = Node(derivation, resolveChildNodes(derivation.output, nodesByInput)) 57 | private def staleOutputsForDerivations(derivations: List[Derivation]): Set[Fact[Any]] = computeAllOutputs(derivations) -- computeAllInputs(derivations) 58 | 59 | @tailrec 60 | private def constructNodes(remainingDerivations: List[Derivation], finishedNodesByInput: Map[Fact[Any], List[Node]], readyFacts: List[Fact[Any]]): Set[Node] = { 61 | readyFacts match { 62 | case rf :: rfs => { 63 | val currentDerivation = remainingDerivations find ( _.output eq rf ) 64 | if (currentDerivation.isEmpty) throw new IllegalStateException("Attempting to process a Derivation which is no longer in the remainingDerivations list. This is weird :)") 65 | 66 | val newRemainingDerivations = remainingDerivations filterNot ( _.output eq rf ) 67 | val newNode: Node = constructNode(currentDerivation.get, finishedNodesByInput) 68 | val newFinishedNodesByInput = finishedNodesByInput map { 69 | case (OriginFact, nodes) if currentDerivation.get.input.isEmpty => (OriginFact, newNode :: nodes) 70 | case (fact, nodes) => (fact, if (currentDerivation.get.input contains fact) newNode :: nodes else nodes) 71 | } 72 | val newReadyFacts = staleOutputsForDerivations(newRemainingDerivations).toList 73 | 74 | constructNodes( newRemainingDerivations, newFinishedNodesByInput, newReadyFacts ) 75 | } 76 | case Nil => if (remainingDerivations.isEmpty) { 77 | finishedNodesByInput.values.foldLeft(Set[Node]())( (acc: Set[Node], v: List[Node]) => acc ++ v ) 78 | } else { 79 | throw new IllegalStateException("There are no stale outputs, but there are remaining derivations. This means there is a cycle in these derivations: " + remainingDerivations) 80 | } 81 | } 82 | } 83 | 84 | /** 85 | * Determines the order in which Facts should be calculated, based on their inputs. It takes a Derivation-graph as constructed by FactEngine.constructGraph 86 | * and orders them in levels. Each level contains Derivation-nodes for which the inputs will have been determined in an earlier level. Following these 87 | * levels while running a derivation cycle will guarantee correct causality between individual Derivations. 88 | * 89 | * @param originalNodes the Set of Nodes describing the dependency graph between Derivations 90 | * @return a List of levels that produce facts required for subsequent levels. Each level is a list of Nodes that can be evaluated once all previous levels 91 | * have been evaluated 92 | */ 93 | def levelSorter(originalNodes: Set[Node]): Levels = { 94 | // TODO : Add detection for an empty level 0, which means we cannot calculate anything without input from other derivations 95 | 96 | @tailrec 97 | def sorter(levelsAcc: Levels, 98 | currentLevel: Level, 99 | higherLevelNodes: List[Node], 100 | completedOutputs: Set[Node], 101 | remainingNodes: List[Node]): Levels = { 102 | (remainingNodes, higherLevelNodes) match { 103 | case ((n @ Node(_, children)) :: ns, _) if (children.toSet -- completedOutputs).isEmpty => 104 | sorter(levelsAcc, n :: currentLevel, higherLevelNodes, completedOutputs, ns) 105 | case (n :: ns, _) => 106 | sorter(levelsAcc, currentLevel, n :: higherLevelNodes, completedOutputs, ns) 107 | case (Nil, remaining @ n :: ns) => 108 | sorter(currentLevel :: levelsAcc, List(), List(), completedOutputs ++ currentLevel, remaining) 109 | case _ => currentLevel :: levelsAcc 110 | } 111 | } 112 | 113 | sorter(List(), List(), List(), Set(), originalNodes.toList) 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/derivations/derivations.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.derivations 2 | 3 | import org.scalarules.derivations.DerivationTools._ 4 | import org.scalarules.engine._ 5 | import org.scalarules.facts.Fact 6 | import org.scalarules.utils.{SourcePosition, SourceUnknown} 7 | 8 | // TODO : Turn this off and fix it 9 | import scala.language.existentials 10 | 11 | /** 12 | * A `Derivation` models the basic element used inside the `Engine`. One execution step of the `Engine` will always 13 | * relate to a single `Derivation`. It defines the `Input`s required for it to be 'ready' as well the `Fact` under 14 | * which the result should be stored inside the context. 15 | */ 16 | sealed trait Derivation { 17 | val input: Input 18 | val output: Output 19 | val condition: Condition 20 | val sourcePosition: SourcePosition = SourceUnknown() 21 | val conditionSourcePosition: SourcePosition = SourceUnknown() 22 | } 23 | 24 | /** 25 | * The default type of `Derivation` is used for normal executions of a single `Evaluation`. 26 | * 27 | * @param input all the input `Fact`s on which this `Derivation` depends. 28 | * @param output the `Fact` under which the result should be stored. 29 | * @param condition the `Condition` which determines whether this particular `Derviation` should be executed at all. 30 | * @param operation the actual `Evaluation` to perform when the `condition` parameter resolves to `true`. Its result 31 | * will be stored under the `Fact` declared in the `output` parameter. 32 | */ 33 | case class DefaultDerivation(input: Input, 34 | output: Output, 35 | condition: Condition, 36 | operation: Evaluation[Any], 37 | override val sourcePosition: SourcePosition = SourceUnknown(), 38 | override val conditionSourcePosition: SourcePosition = SourceUnknown()) extends Derivation 39 | 40 | /** 41 | * Special case for a sub `Derivation`. `Derivation`s of this type cause a nested run of the `Engine` to be performed 42 | * and some of the results of that run will end up being the result of this `Derivation`. 43 | * 44 | * Note: this type of `Derivation` is currently targeted at handling a `Fact` with a `List` as value type. The 45 | * `Derivation`s specified in this `SubRunDerivation` will be performed for each value inside that `List`. 46 | * 47 | * @param inputs all the input `Fact`s on which this `Derivation` depends directly. Dependencies of the inner `Derivations` 48 | * will be determined automatically and added to the the value of this parameter to form the complete 49 | * set of `Fact`s on which this `Derivation` depends. 50 | * @param output the `Fact` under which the result should be stored. 51 | * @param condition the `Condition` which determines whether this particular `Derviation` should be executed at all. 52 | * @param subRunData describes how to setup the nested execution and how to extract the result from it. 53 | */ 54 | case class SubRunDerivation(inputs: Input, output: Output, condition: Condition, subRunData: SubRunData[Any, _]) extends Derivation { 55 | private val outsideFactsInSubRun: Set[Fact[Any]] = computeAllInputs(subRunData.derivations) -- computeAllOutputs(subRunData.derivations) - subRunData.yieldFact 56 | val input = (subRunData.inputList :: inputs ++ outsideFactsInSubRun).distinct 57 | } 58 | 59 | /** 60 | * Specifies the `Derivation`s to run within a `SubRunDerivation` and how to handle the `Context` around its execution. 61 | * 62 | * @param derivations a `List` of `Derivation`s which must be executed in a nested run of the `Engine`. 63 | * @param contextAdditions a function to enhance the `Context` based on the input value for a particular run. 64 | * @param inputList the `Fact` whose value should be used to iterate over and perform sub runs for. 65 | * @param yieldFact the `Fact` to extract from the result of the sub run. These values will be collected and end up 66 | * being the overall result of the `SubRunDerivation`. 67 | * @tparam O type of the resulting output `Fact`. 68 | * @tparam I element type of the input `Fact`. 69 | */ 70 | case class SubRunData[+O, I](derivations: List[Derivation], contextAdditions: I => Context, inputList: Fact[List[I]], yieldFact: Fact[O]) { 71 | def yieldValue: Context => Option[O] = c => yieldFact.toEval(c) 72 | } 73 | -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/engine/Conditions.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.engine 2 | 3 | import org.scalarules.facts.Fact 4 | 5 | object Conditions { 6 | 7 | val trueCondition: Condition = (c: Context) => true 8 | 9 | def exists[A](fact: Fact[A]): Condition = c => fact.toEval.apply(c).isDefined 10 | def notExists[A](fact: Fact[A]): Condition = c => fact.toEval.apply(c).isEmpty 11 | 12 | def equalsCondition[A](lhs:Evaluation[A], rhs: Evaluation[A]): Condition = (c: Context) => (lhs(c), rhs(c)) match { 13 | case (Some(x), Some(y)) => x == y 14 | case _ => false 15 | } 16 | 17 | def andCondition(lhs: Condition, rhs: Condition): Condition = (c: Context) => lhs(c) && rhs(c) 18 | 19 | def orCondition(lhs: Condition, rhs: Condition): Condition = (c: Context) => lhs(c) || rhs(c) 20 | 21 | } 22 | 23 | -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/engine/LRUCache.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.engine 2 | 3 | import scala.collection.concurrent.TrieMap 4 | 5 | class LRUCache[K, V](val maxSize: Int = LRUCache.GRAPH_CACHE_MAX_SIZE, val cleanupInterval: Long = LRUCache.GRAPH_CACHE_CLEANUP_INTERVAL_MS) { 6 | 7 | private var graphCache: TrieMap[K, (V, Long)] = TrieMap() 8 | private var lastCacheCleanup = System.currentTimeMillis() 9 | 10 | def add(key: K, value: V): V = { 11 | updateCacheWithNewEntry(key, value) 12 | value 13 | } 14 | 15 | def get(key: K): Option[V] = graphCache.get(key).map{ case (cacheEntry, timestamp) => cacheEntry } 16 | 17 | def size: Int = graphCache.size 18 | 19 | private def updateCacheWithNewEntry(key: K, value: V): Unit = { 20 | if ((System.currentTimeMillis() - lastCacheCleanup) > maxSize) { 21 | // Cleaning up the cache before adding anything new to it 22 | val cacheEntriesSortedByAge: List[(K, (V, Long))] = graphCache.toList.sortBy[Long]( entry => entry._2._2 ) 23 | val removalCandidates = 24 | if (cacheEntriesSortedByAge.size > maxSize) 25 | cacheEntriesSortedByAge.takeRight(cacheEntriesSortedByAge.size - maxSize) 26 | else 27 | List() 28 | 29 | removalCandidates.map{ case (key, value) => graphCache -= key } 30 | 31 | lastCacheCleanup = System.currentTimeMillis() 32 | } 33 | 34 | graphCache += key -> ((value, System.currentTimeMillis())) 35 | } 36 | 37 | 38 | } 39 | 40 | object LRUCache { 41 | val GRAPH_CACHE_MAX_SIZE: Int = 100 42 | val GRAPH_CACHE_CLEANUP_INTERVAL_MS: Long = 5000 43 | } 44 | -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/engine/evaluations.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.engine 2 | 3 | import org.scalarules.facts.Fact 4 | 5 | trait Evaluation[+A] { 6 | def apply(c: Context): Option[A] 7 | 8 | def asListEvaluation: Evaluation[List[A]] = new ListEvaluationWrapper(this) 9 | } 10 | 11 | class NoopEvaluation[+A] extends Evaluation[A] { 12 | def apply(c: Context): Option[A] = None 13 | 14 | override def toString: String = "None" 15 | } 16 | 17 | class ErrorEvaluation[+A](val message: String) extends Evaluation[A] { 18 | def apply(c: Context): Option[A] = throw new IllegalStateException(message) 19 | 20 | override def toString: String = "Error : " + message 21 | } 22 | 23 | class ListEvaluationWrapper[+A](wrappee: Evaluation[A]) extends Evaluation[List[A]] { 24 | def apply(c: Context): Option[List[A]] = wrappee(c) match { 25 | case Some(x) => Some(List(x)) 26 | case None => None 27 | } 28 | } 29 | 30 | class ConstantValueEvaluation[+A](v: A) extends Evaluation[A] { 31 | def apply(c: Context): Option[A] = Some(v) 32 | 33 | override def toString: String = v.toString 34 | } 35 | 36 | class SingularFactEvaluation[+A](fact: Fact[A]) extends Evaluation[A] { 37 | def apply(c: Context): Option[A] = c.get(fact).asInstanceOf[Option[A]] 38 | 39 | override def toString: String = fact.name 40 | } 41 | 42 | class ListFactEvaluation[+A](fact: Fact[List[A]]) extends Evaluation[List[A]] { 43 | def apply(c: Context): Option[List[A]] = c.get(fact).asInstanceOf[Option[List[A]]] 44 | 45 | override def toString: String = fact.name 46 | } 47 | 48 | class ProjectionEvaluation[-A, +B](src: Evaluation[A], f: A => B) extends Evaluation[B] { 49 | override def apply(c: Context): Option[B] = src(c) match { 50 | case Some(x) => Some(f(x)) 51 | case None => None 52 | } 53 | } 54 | 55 | class ProjectionListEvaluation[-A, +B](src: Evaluation[List[A]], f: A => B) extends Evaluation[List[B]] { 56 | override def apply(c: Context): Option[List[B]] = src(c) match { 57 | case Some(x) => Some(x.map(f)) 58 | case None => None 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/engine/metadata.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.engine 2 | 3 | import org.scalarules.derivations.Derivation 4 | 5 | trait Step { 6 | val initialContext: Context 7 | val derivation: Derivation 8 | val resultContext: Context 9 | } 10 | 11 | trait NoChangesStep extends Step { 12 | override lazy val resultContext: Context = initialContext 13 | } 14 | 15 | case class AlreadyExistsStep(initialContext: Context, derivation: Derivation) extends NoChangesStep 16 | case class ConditionFalseStep(initialContext: Context, derivation: Derivation) extends NoChangesStep 17 | case class EmptyResultStep(initialContext: Context, derivation: Derivation) extends NoChangesStep 18 | 19 | trait ChangesStep extends Step 20 | 21 | case class EvaluatedStep(initialContext: Context, derivation: Derivation, resultContext: Context) extends ChangesStep 22 | case class IterationFinishedStep(initialContext: Context, derivation: Derivation, resultContext: Context) extends ChangesStep 23 | case class IterationStartedStep(initialContext: Context, derivation: Derivation, resultContext: Context) extends ChangesStep 24 | -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/engine/package.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules 2 | 3 | import org.scalarules.derivations.Node 4 | import org.scalarules.facts.Fact 5 | 6 | package object engine { 7 | 8 | type Context = Map[Fact[Any], Any] 9 | type Condition = Context => Boolean 10 | type Input = List[Fact[Any]] 11 | type Output = Fact[Any] 12 | type Level = List[Node] 13 | type Levels = List[Level] 14 | 15 | } 16 | -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/facts/facts.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.facts 2 | 3 | import org.scalarules.engine.{ErrorEvaluation, Evaluation, ListFactEvaluation, SingularFactEvaluation} 4 | 5 | import scala.language.existentials 6 | 7 | trait Fact[+A] { 8 | def name: String 9 | def description: String 10 | 11 | def toEval: Evaluation[A] 12 | 13 | def valueType: String 14 | 15 | override def toString: String = name 16 | } 17 | 18 | case class SingularFact[+A](name: String, description: String = "", valueType: String = "") extends Fact[A] { 19 | def toEval: Evaluation[A] = new SingularFactEvaluation(this) 20 | } 21 | 22 | case class ListFact[+A](name: String, description: String = "", valueType: String = "") extends Fact[List[A]] { 23 | def toEval: Evaluation[List[A]] = new ListFactEvaluation[A](this) 24 | } 25 | 26 | case object OriginFact extends Fact[Nothing] { 27 | def name: String = "___meta___OriginFact___meta___" 28 | def description: String = "Meta-fact used in graph construction" 29 | 30 | def toEval: Evaluation[Nothing] = new ErrorEvaluation("The OriginFact is a meta-fact used in graph construction to indicate top-level constant evaluations") 31 | 32 | def valueType: String = "Nothing" 33 | } 34 | 35 | case class SynthesizedFact[+A](factOriginalFact: Fact[Any], synthesizedPostfix: String, description: String = "", valueType: String = "") extends Fact[A] { 36 | def name: String = factOriginalFact.name + "_" + synthesizedPostfix 37 | 38 | def toEval: Evaluation[A] = new SingularFactEvaluation[A](this) 39 | } 40 | -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/utils/FactMacros.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.utils 2 | 3 | import org.scalarules.facts.{ListFact, SingularFact} 4 | 5 | import scala.annotation.compileTimeOnly 6 | import scala.language.experimental.macros 7 | import scala.reflect.macros.blackbox._ 8 | 9 | /** 10 | * `FactMacros` contains two compile-time macros which allow the easy creation of a `Fact`, without having to explicitly repeat 11 | * the name of the `Fact` as an argument. This makes it easier to define Facts for use in a DSL, as the name of the val to 12 | * which it is assigned, becomes the name of the `Fact` itself. 13 | * 14 | * Without these macros, you would need to create `Fact`s as follows: 15 | * 16 | * `val MyFact = new SingularFact[Int]("MyFact")` 17 | * 18 | * This redundancy of the `Fact`'s name is not just a nuisance to type, it actually causes trouble when renaming the `Fact`, 19 | * or trying to copy its declaration. In both situations, you *must* also alter the name parameter manually. The compiler 20 | * and IDE tooling cannot help you track these errors down and so you are bound to find out about them at runtime, with 21 | * potentially uninformative error messages. 22 | * 23 | * If instead, you use these macros, these troubles will be taken away. Define your `Fact`s like this and have the IDE and 24 | * compiler warn you about duplicate names etc: 25 | * 26 | * `val MyFact = FactMacros.defineFact[Int]("Description")` 27 | * 28 | */ 29 | object FactMacros { 30 | 31 | // Macro definitions 32 | 33 | /** 34 | * Defines a `Fact`, using the name of the `val` it is assigned to as the name of the `Fact`. Note: the value of this 35 | * macro *must* be assigned to a `val`, otherwise a compiler error will be raised. 36 | * 37 | * @tparam A the value type of the resulting `Fact`. 38 | * @return a `SingularFact` initialized with the name of the val declaration. 39 | */ 40 | def defineFact[A](): SingularFact[A] = macro FactMacros.defineFactMacroImpl[A] 41 | 42 | /** 43 | * Defines a `Fact`, using the name of the `val` it is assigned to as the name of the `Fact`. Note: the value of this 44 | * macro *must* be assigned to a `val`, otherwise a compiler error will be raised. 45 | * 46 | * @tparam A the value type of the resulting `Fact`. 47 | * @param description description of the `Fact`, to be passed along to the `Fact`'s constructor. 48 | * @return a `SingularFact` initialized with the name of the val declaration. 49 | */ 50 | def defineFactWithDescription[A](description: String): SingularFact[A] = macro FactMacros.defineFactMacroWithDescriptionImpl[A] 51 | 52 | /** 53 | * Defines a `Fact`, using the name of the `val` it is assigned to as the name of the `Fact`. Note: the value of this 54 | * macro *must* be assigned to a `val`, otherwise a compiler error will be raised. 55 | * 56 | * @tparam A the value type of the resulting `Fact`. 57 | * @return a `ListFact` initialized with the name of the val declaration. 58 | */ 59 | def defineListFact[A](): ListFact[A] = macro FactMacros.defineListFactMacroImpl[A] 60 | 61 | /** 62 | * Defines a `Fact`, using the name of the `val` it is assigned to as the name of the `Fact`. Note: the value of this 63 | * macro *must* be assigned to a `val`, otherwise a compiler error will be raised. 64 | * 65 | * @tparam A the value type of the resulting `Fact`. 66 | * @param description description of the `Fact`, to be passed along to the `Fact`'s constructor. 67 | * @return a `ListFact` initialized with the name of the val declaration. 68 | */ 69 | def defineListFactWithDescription[A](description: String): ListFact[A] = macro FactMacros.defineListFactMacroWithDescriptionImpl[A] 70 | 71 | 72 | // Macro implementations 73 | 74 | @compileTimeOnly("This is a compile-time macro implementation, do not call it at runtime") 75 | def defineFactMacroImpl[A : c.WeakTypeTag](c: Context)(): c.Expr[SingularFact[A]] = 76 | { 77 | val factNameExpr = extractDeclaredValName(c) 78 | val valueType = extractValueType[A](c) 79 | c.universe.reify { new SingularFact[A](factNameExpr.splice, "", valueType.splice) } 80 | } 81 | 82 | @compileTimeOnly("This is a compile-time macro implementation, do not call it at runtime") 83 | def defineFactMacroWithDescriptionImpl[A : c.WeakTypeTag](c: Context)(description: c.Expr[String]): c.Expr[SingularFact[A]] = 84 | { 85 | val factNameExpr = extractDeclaredValName(c) 86 | val valueType = extractValueType[A](c) 87 | c.universe.reify { new SingularFact[A](factNameExpr.splice, description.splice, valueType.splice) } 88 | } 89 | 90 | @compileTimeOnly("This is a compile-time macro implementation, do not call it at runtime") 91 | def defineListFactMacroImpl[A : c.WeakTypeTag](c: Context)(): c.Expr[ListFact[A]] = 92 | { 93 | val factNameExpr = extractDeclaredValName(c) 94 | val valueType = extractValueType[A](c) 95 | c.universe.reify { new ListFact[A](factNameExpr.splice, "", valueType.splice) } 96 | } 97 | 98 | @compileTimeOnly("This is a compile-time macro implementation, do not call it at runtime") 99 | def defineListFactMacroWithDescriptionImpl[A : c.WeakTypeTag](c: Context)(description: c.Expr[String]): c.Expr[ListFact[A]] = 100 | { 101 | val factNameExpr = extractDeclaredValName(c) 102 | val valueType = extractValueType[A](c) 103 | c.universe.reify { new ListFact[A](factNameExpr.splice, description.splice, valueType.splice) } 104 | } 105 | 106 | /** 107 | * Helper function to extract the name of the declaring val from the macro `Context`. 108 | * 109 | * @param c `Context` provided by the compiler at the time of macro invocation. 110 | * @return an `Expr[String]` containing the name of the declaring val. 111 | */ 112 | private def extractDeclaredValName(c: Context): c.Expr[String] = { 113 | import c.universe._ // scalastyle:ignore 114 | 115 | val enclosingOwner: _root_.scala.reflect.macros.blackbox.Context#Symbol = c.internal.enclosingOwner 116 | if (!enclosingOwner.isTerm || !enclosingOwner.asInstanceOf[TermSymbol].isVal) { 117 | c.abort(c.enclosingPosition, "Facts should be assigned directly to a val") 118 | } 119 | 120 | val fullName = enclosingOwner.name.decodedName.toString().trim() 121 | 122 | c.Expr[String](Literal(Constant(fullName))) 123 | } 124 | 125 | private def extractValueType[A : c.WeakTypeTag](c: Context): c.Expr[String] = { 126 | import c.universe._ // scalastyle:ignore 127 | 128 | c.Expr[String](Literal(Constant(c.weakTypeOf[A].toString))) 129 | } 130 | 131 | } 132 | 133 | -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/utils/PrettyPrinter.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.utils 2 | 3 | import org.scalarules.derivations.DerivationTools._ 4 | import org.scalarules.derivations.{DefaultDerivation, Derivation, SubRunDerivation} 5 | import org.scalarules.engine._ 6 | import org.scalarules.facts.Fact 7 | 8 | import scala.annotation.tailrec 9 | 10 | object PrettyPrinter { 11 | 12 | val done = "Done." 13 | 14 | /** 15 | * Creates a pretty String containing all the proposed calculation levels, which will give you an idea of the order in which Facts will be calculated. 16 | * 17 | * @param levels the levels as determined by FactEngine.levelSorter 18 | * @return a nicely formatted String containing the proposed calculation order 19 | */ 20 | def printLevels(levels: Levels): String = { 21 | @tailrec 22 | def go(levelIndex: Int, levels: Levels, acc: String): String = levels match { 23 | case l :: ls => { 24 | val levelLine = l.foldLeft(s"Level $levelIndex\n")((s, n) => s + s" Fact: ${n.derivation.output}, # children: ${n.children.size}\n") 25 | go(levelIndex + 1, ls, acc + levelLine) 26 | } 27 | case _ => acc + done 28 | } 29 | 30 | go(0, levels, "\nProposed calculation order:\n") 31 | } 32 | 33 | def printContext(c: Context): String = { 34 | @tailrec 35 | def go(facts: List[(Fact[Any], Any)], acc: String): String = facts match { 36 | case f :: fs => go(fs, acc + s" ${f._1} = ${f._2}\n") 37 | case _ => acc + done 38 | } 39 | 40 | val listOfFacts: List[(Fact[Any], Any)] = c.toList.sorted(Ordering.by[(Fact[Any], Any), String](_._1.name)) 41 | 42 | go(listOfFacts, "\nValues in context:\n") 43 | } 44 | 45 | def printSteps(steps: List[Step]): String = { 46 | @tailrec 47 | def go(remainingSteps: List[Step], acc: String): String = remainingSteps match { 48 | case AlreadyExistsStep(initial, derivation) :: tail => 49 | go(tail, s" * Evaluate: ${derivation.output.name}\n * Result: Fact already exists in context: Skipping " + 50 | s"* Existing value: ${Map(derivation.output.name -> initial.get(derivation.output).get)}\n" + acc) 51 | case ConditionFalseStep(initial, derivation) :: tail => 52 | go(tail, s" * Evaluate: ${derivation.output.name}\n * Result: Condition false: Skipping * Change: No change!\n" + acc) 53 | case EmptyResultStep(initial, derivation) :: tail => 54 | go(tail, s" * Evaluate: ${derivation.output.name}\n * Result: Empty * Change: No change!\n" + acc) 55 | case EvaluatedStep(initial, derivation, result) :: tail => 56 | go(tail, s" * Evaluate: ${derivation.output.name}\n * Result: Evaluated * Change: ${result -- initial.keys}\n" + acc) 57 | case IterationFinishedStep(initial, derivation, result) :: tail => 58 | go(tail, s" * Evaluate: ${derivation.output.name}\n * Result: Iteration completed * Change: ${result -- initial.keys}\n" + acc) 59 | case IterationStartedStep(initial, derivation, result) :: tail => 60 | go(tail, s" * Evaluate: ${derivation.output.name}\n * Result: Iteration started * Change: ${result -- initial.keys}\n" + acc) 61 | case _ => 62 | acc + done 63 | } 64 | 65 | "\nSteps taken:\n" + go(steps, "") 66 | } 67 | 68 | def toJson(derivations: List[Derivation]): String = { 69 | val nodes = (computeAllInputs(derivations) ++ computeAllOutputs(derivations)).map( _.name ).toList 70 | val edges = derivations.flatMap(d => d.input.map(i => (i.name, d.output.name) ) ) 71 | 72 | val inputs = (computeAllInputs(derivations) -- computeAllOutputs(derivations)).map( _.name ) 73 | val outputs = (computeAllOutputs(derivations) -- computeAllInputs(derivations)).map( _.name ) 74 | 75 | val nodeToExpressionsMap = derivations.map( d => (d.output.name, d match {case der: DefaultDerivation => der.operation.toString; case der: SubRunDerivation => "SubRun"}) ).toMap 76 | def nodeToExpressions(nodeName: String) = if (nodeToExpressionsMap contains nodeName) nodeToExpressionsMap(nodeName) else "" 77 | def nodeToType(nodeName: String): String = if (inputs contains nodeName) "Input" 78 | else if (outputs contains nodeName) "Output" 79 | else "Intermediate" 80 | 81 | val nodesWithIndex = nodes.zipWithIndex 82 | val nodeToIndexMap = nodesWithIndex.toMap 83 | 84 | val nodesJson = nodesWithIndex.map{ case (n, i) => buildNodeJson(i, n, nodeToType(n), nodeToExpressions(n)) }.mkString("\"nodes\": [", ", ", "]") 85 | val edgesJson = edges.map{ case (source, target) => buildEdgeJson(source, target, nodeToIndexMap) }.mkString("\"links\": [", ", ", "]") 86 | 87 | val json = s"{${nodesJson}, ${edgesJson}}" 88 | 89 | json 90 | } 91 | 92 | private def buildNodeJson(nodeIndex: Int, nodeName: String, nodeType: String, nodeDescription: String): String = { 93 | s"""{ 94 | | "node": "${nodeIndex}", 95 | | "name": "${nodeName}", 96 | | "type": "${nodeType}", 97 | | "description": "${nodeDescription}" 98 | | } 99 | """.stripMargin 100 | } 101 | 102 | private def buildEdgeJson(source: String, target: String, nodeToIndexMap: Map[String, Int]): String = { 103 | s"""{ 104 | | "id": "${source}-${target}", 105 | | "source": ${nodeToIndexMap(source)}, 106 | | "target": ${nodeToIndexMap(target)}, 107 | | "value": 1 108 | |} 109 | """.stripMargin 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /engine-core/src/main/scala/org/scalarules/utils/SourcePosition.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.utils 2 | 3 | 4 | sealed trait SourcePosition { 5 | val offset: Int 6 | } 7 | 8 | case class SourceUnknown() extends SourcePosition { 9 | override val offset: Int = 0 10 | } 11 | case class FileSourcePosition(fileName: String, line: Int, column: Int, offset: Int, length: Int) extends SourcePosition 12 | -------------------------------------------------------------------------------- /engine-core/src/test/scala/org/scalarules/engine/FactEngineTestGlossary.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.engine 2 | 3 | import org.scalarules.facts.{ListFact, SingularFact} 4 | 5 | object FactEngineTestGlossary { 6 | val Costs = SingularFact[BigDecimal]("Costsp") 7 | val EstimatedNewValue = SingularFact[BigDecimal]("EstimatedNewValue") 8 | val EstimatedValue = SingularFact[BigDecimal]("EstimatedValue") 9 | val InterestValue = SingularFact[BigDecimal]("InterestValue") 10 | val PurchaseAmount = SingularFact[BigDecimal]("PurchaseAmount") 11 | val LumpSum = SingularFact[BigDecimal]("LumpSum") 12 | val GoodsValue = SingularFact[BigDecimal]("GoodsValue") 13 | val MarketValue = SingularFact[BigDecimal]("MarketValue") 14 | val MarketValueAlternative = SingularFact[BigDecimal]("MarketValueAlternative") 15 | val MarketValueAdded = SingularFact[BigDecimal]("MarketValueAdded") 16 | val MarketValueReduced = SingularFact[BigDecimal]("MarketValueReduced") 17 | val BuildingValue = SingularFact[BigDecimal]("BuildingValue") 18 | val PropertyValue = SingularFact[BigDecimal]("PropertyValue") 19 | val ConstructionValue = SingularFact[BigDecimal]("ConstructionValue") 20 | val ConstructionCost = SingularFact[BigDecimal]("ConstructionCost") 21 | val BuildingCost = SingularFact[BigDecimal]("BuildingCost") 22 | val TaxationValue = SingularFact[BigDecimal]("TaxationValue") 23 | val SubRunInput = ListFact[BigDecimal]("SubRunInput") 24 | val SubRunOutput = ListFact[BigDecimal]("SubRunOutput") 25 | val SubRunIntermediateInput = SingularFact[BigDecimal]("SubRunIntermediateInput") 26 | val SubRunIntermediateResult = SingularFact[BigDecimal]("SubRunIntermediateResult") 27 | 28 | val SpeedOfLightMetresPerSecondConstant = SingularFact[BigDecimal]("SpeedOfLightMetresPerSecondConstant") 29 | val TimeLightTravelsToEarthInSecondsConstant = SingularFact[BigDecimal]("TimeLightTravelsToEarthInSecondsConstant") 30 | val DistanceBetweenEarthAndSunInMetres = SingularFact[BigDecimal]("DistanceBetweenEarthAndSunInMetres") 31 | } 32 | -------------------------------------------------------------------------------- /engine-core/src/test/scala/org/scalarules/engine/FactTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.engine 2 | 3 | import org.scalarules.facts.SingularFact 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | class FactTest extends FlatSpec with Matchers { 7 | val context: Context = Map(SingularFact("naamAanwezig") -> 42, SingularFact("onverwachtObject") -> "String") 8 | 9 | it should "return a Some(Bedrag) when given a String that is present in the context" in { 10 | val optie = SingularFact("naamAanwezig").toEval 11 | optie(context) should be (Some(42)) 12 | } 13 | 14 | it should "return a None when given a String that is not present in the context" in { 15 | val optie = SingularFact("naamNietAanwezig").toEval 16 | optie(context) should be (None) 17 | } 18 | 19 | it should "not throw an exception when given a context-String that results in a different type than expected" in { 20 | val optie = SingularFact[Int]("onverwachtObject").toEval 21 | optie(context) should be (Some("String")) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /engine-core/src/test/scala/org/scalarules/engine/LRUCacheTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.engine 2 | 3 | import org.scalatest.{FlatSpec, Matchers} 4 | 5 | class LRUCacheTest extends FlatSpec with Matchers { 6 | 7 | it should "cleanup older items back to initial size" in { 8 | val cache: LRUCache[Int, Int] = new LRUCache(4, 10) 9 | 10 | (0 until 10).foreach( (n: Int) => cache.add(n, n) ) 11 | 12 | Thread.sleep(20) 13 | 14 | cache.add(15, 15) 15 | 16 | cache.size should be (5) 17 | cache.get(15) should be(Some(15)) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /engine-core/src/test/scala/org/scalarules/engine/SubRunDerivationTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.engine 2 | 3 | import org.scalarules.derivations.{DefaultDerivation, SubRunData, SubRunDerivation} 4 | import org.scalarules.facts.{ListFact, SingularFact} 5 | import org.scalatest.{FlatSpec, Matchers} 6 | 7 | class SubRunDerivationTest extends FlatSpec with Matchers { 8 | 9 | private val condition: Condition = c => true 10 | private val outputSub1 = SingularFact[Int]("SubDer1Output") 11 | private val inputSub1 = SingularFact[Int]("SubDer1Input1") 12 | private val input1Sub2 = SingularFact[Int]("SubDer2Input1") 13 | private val input2Sub2 = SingularFact[Int]("SubDer2Input2") 14 | private val derivation1 = DefaultDerivation(List(inputSub1), outputSub1, condition, new NoopEvaluation) 15 | private val derivation2 = DefaultDerivation(List(input1Sub2, input2Sub2, outputSub1), SingularFact[Int]("SubDer2Output"), condition, new NoopEvaluation) 16 | private val subDerivations = List(derivation1, derivation2) 17 | 18 | it should "Take inputs of subcalculations into account when returning input" in { 19 | val mainInput = SingularFact[Int]("MainInputFact") 20 | val listInput = ListFact[String]("ListInput") 21 | val derivation: SubRunDerivation = SubRunDerivation(List(mainInput), SingularFact[Int]("MainOutputFact"), condition, 22 | SubRunData[Int, String](subDerivations, _ => Map(), listInput, SingularFact[Int]("SubDer2Output"))) 23 | derivation.input should contain only(mainInput, listInput, inputSub1, input1Sub2, input2Sub2) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /engine-test-utils/src/main/scala/org/scalarules/testutils/TestUtils.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.testutils.nl 2 | 3 | import org.scalarules.derivations.Derivation 4 | import org.scalarules.engine.{FactEngine, Step, _} 5 | import org.scalarules.facts.Fact 6 | import org.scalarules.services.{AnalyzedScenario, HeuristicService} 7 | import org.scalarules.utils.PrettyPrinter 8 | 9 | object TestUtils { 10 | 11 | def run(initial: Context, derivations: List[Derivation]): Context = FactEngine.runNormalDerivations(initial, derivations) 12 | def run(initial: Context, derivations: List[Derivation], heuristicService: HeuristicService): Option[AnalyzedScenario] = heuristicService.runHeuristics(initial, run(_, derivations)) 13 | def debug(initial: Context, derivations: List[Derivation]): Context = { 14 | val result: (Context, List[Step]) = FactEngine.runDebugDerivations(initial, derivations) 15 | println(PrettyPrinter.printSteps(result._2)) 16 | result._1 17 | } 18 | def debug(initial: Context, derivations: List[Derivation], heuristicService: HeuristicService): Option[AnalyzedScenario] = heuristicService.runHeuristics(initial, debug(_, derivations)) 19 | 20 | 21 | def runAndExtractFact[A](initial: Context, derivations: List[Derivation], extract: Fact[A]): Option[A] = { 22 | extract.toEval( run(initial, derivations) ) 23 | } 24 | 25 | def debugAndExtractFact[A](initial: Context, derivations: List[Derivation], extract: Fact[A]): Option[A] = { 26 | extract.toEval( debug(initial, derivations) ) 27 | } 28 | 29 | def runAndExtractFact[A](initial: Context, derivations: List[Derivation], heuristicService: HeuristicService, extract: Fact[A]): Option[A] = { 30 | extract.toEval( run(initial, derivations, heuristicService).get.result ) 31 | } 32 | 33 | def debugAndExtractFact[A](initial: Context, derivations: List[Derivation], heuristicService: HeuristicService, extract: Fact[A]): Option[A] = { 34 | extract.toEval( debug(initial, derivations, heuristicService).get.result ) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /engine-test-utils/src/main/scala/org/scalarules/testutils/nl/HeuristicTester.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.testutils.nl 2 | 3 | import org.scalarules.dsl.nl.grammar.Berekening 4 | import org.scalarules.engine.{Context, FactEngine} 5 | import org.scalarules.facts.Fact 6 | import org.scalarules.services._ 7 | import org.scalarules.utils.PrettyPrinter 8 | 9 | //scalastyle:off null 10 | 11 | class HeuristicTester(heuristicService: HeuristicService, verplichteBerekening: Berekening, optioneleBerekeningen: Berekening*) 12 | extends BerekeningenTester(verplichteBerekening, optioneleBerekeningen:_*) { 13 | 14 | override def runTest(description: String, context: Context, factValues: Seq[FactValues]): Unit = it should description in { 15 | val heuristicResult: Option[AnalyzedScenario] = heuristicService.runHeuristics(context, FactEngine.runNormalDerivations(_, berekeningen)) 16 | 17 | if (factValues == null) { 18 | withClue("Expecting to find no resulting scenario, but a result was returned: ") { 19 | heuristicResult should be(None) 20 | } 21 | } else { 22 | withClue("Expecting to find a resulting scenario, but none was found: ") { 23 | heuristicResult should not be (None) 24 | } 25 | 26 | val verwachteWaardes: Seq[(Fact[Any], Any)] = factValues flatMap (_.tuples) 27 | verwachteWaardes foreach { factValue => assert(heuristicResult.get.result, factValue._1, factValue._2) } 28 | } 29 | } 30 | 31 | override def debugTest(description: String, context: Context, factValues: Seq[FactValues]): Unit = it should description in { 32 | val heuristicResult: Option[AnalyzedScenario] = heuristicService.runHeuristics(context, c => { 33 | val debugResult = FactEngine.runDebugDerivations(c, berekeningen) 34 | println(PrettyPrinter.printSteps(debugResult._2)) 35 | debugResult._1 36 | }) 37 | 38 | if (factValues == null) 39 | heuristicResult should be (None) 40 | else { 41 | heuristicResult should not be (None) 42 | 43 | val verwachteWaardes: Seq[(Fact[Any], Any)] = factValues flatMap (_.tuples) 44 | verwachteWaardes foreach { factValue => assert(heuristicResult.get.result, factValue._1, factValue._2) } 45 | } 46 | } 47 | 48 | implicit class ExtendedResultOfGegeven(resultOfGegeven: ResultOfGegeven) { 49 | def verwachtGeenResultaat(): Unit = resultOfGegeven.tester.runTest(resultOfGegeven.description, resultOfGegeven.context, null) 50 | def debugGeenResultaat(): Unit = resultOfGegeven.tester.debugTest(resultOfGegeven.description, resultOfGegeven.context, null) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /engine-test-utils/src/main/scala/org/scalarules/testutils/nl/TestDsl.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.testutils.nl 2 | 3 | import org.scalarules.finance.nl.{Bedrag, Per} 4 | import org.scalarules.dsl.nl.grammar.{Aanwezigheid, Berekening} 5 | import org.scalarules.engine.{Context, FactEngine} 6 | import org.scalarules.facts.Fact 7 | import org.scalarules.utils.PrettyPrinter 8 | import org.scalatest.{FlatSpec, Matchers} 9 | 10 | // TODO : Make into English and move the Dutch specific parts to a Dutch dsl package 11 | class BerekeningenTester(verplichteBerekening: Berekening, optioneleBerekeningen: Berekening*) extends FlatSpec with Matchers { 12 | 13 | val berekeningen = (verplichteBerekening :: optioneleBerekeningen.toList).flatMap(_.berekeningen) 14 | def waardes(factValues: FactValues*): FactValues = FactValues( factValues flatMap (_.tuples) ) 15 | def test(description: String): ResultOfTest = new ResultOfTest(description, this) 16 | 17 | def runTest(description: String, context: Context, factValues: Seq[FactValues]): Unit = it should description in { 18 | val result = FactEngine.runNormalDerivations(context, berekeningen) 19 | 20 | val verwachteWaardes: Seq[(Fact[Any], Any)] = factValues flatMap (_.tuples) 21 | verwachteWaardes foreach { factValue => assert(result, factValue._1, factValue._2) } 22 | } 23 | 24 | def debugTest(description: String, context: Context, factValues: Seq[FactValues]): Unit = it should description in { 25 | val result = FactEngine.runDebugDerivations(context, berekeningen) 26 | println(PrettyPrinter.printSteps(result._2)) 27 | 28 | val verwachteWaardes: Seq[(Fact[Any], Any)] = factValues flatMap (_.tuples) 29 | verwachteWaardes foreach { factValue => assert(result._1, factValue._1, factValue._2) } 30 | } 31 | 32 | protected def assert[A](result: Context, fact: Fact[A], value: A) = { 33 | fact.toEval(result) match { 34 | case Some(x) if value == None => fail(s"Feit ${fact.name} wordt verwacht niet aanwezig te zijn, maar heeft waarde $x") 35 | case Some(x) => assertValue(x, value) 36 | case None if value == None => // What to do?? --> Nothing.. You interpret Nil as 'expected not to be present', which is exactly what the None result means 37 | case _ => fail(s"Feit ${fact.name} is niet beschikbaar in het resultaat. Waarde $value werd verwacht") 38 | } 39 | } 40 | 41 | // TODO : Extract these into ValueInspectors which we can add new instances of to some map/list of inspectors (maybe even just integrate with ScalaTest) 42 | private def assertValue[A](actual: A, expected: A): Unit = { 43 | actual match { 44 | case x: List[Any] if expected != None => { 45 | assert(x.length == expected.asInstanceOf[List[Any]].length, "Lists sizes mismatch") 46 | x.zip(expected.asInstanceOf[List[Any]]).foreach( a => assertValue (a._1, a._2) ) 47 | } 48 | case x: BigDecimal if expected != None => x.setScale(24, BigDecimal.RoundingMode.HALF_EVEN) should be (expected.asInstanceOf[BigDecimal].setScale(24, BigDecimal.RoundingMode.HALF_EVEN)) 49 | case x: Bedrag if expected != None => x.afgerondOpCenten should be (expected.asInstanceOf[Bedrag].afgerondOpCenten) 50 | // TODO: This is a bit ugly ... maybe we have to create a Per-companion object with appropriate unapply methods 51 | case x: Per[_, _] if expected != None && x.waarde.isInstanceOf[Bedrag] => { 52 | val concreteExpected: Per[Bedrag, _] = expected.asInstanceOf[Per[Bedrag, _]] 53 | x.waarde.asInstanceOf[Bedrag].afgerondOpCenten should be (concreteExpected.waarde.afgerondOpCenten) 54 | x.termijn should be (concreteExpected.termijn) 55 | } 56 | case x: Any if expected != None => x should be (expected) 57 | } 58 | } 59 | 60 | implicit class FactToFactValues[A](fact: Fact[A]) { 61 | def is(value: A): FactValues = FactValues(List((fact, value))) 62 | def niet(aanwezigheid: Aanwezigheid): FactValues = FactValues(List((fact, None))) 63 | } 64 | } 65 | 66 | case class FactValues(tuples: Seq[(Fact[Any], Any)]) 67 | 68 | class ResultOfTest (description: String, tester: BerekeningenTester) extends Matchers { 69 | def gegeven(factValues: FactValues*): ResultOfGegeven = new ResultOfGegeven(factValues.flatMap(_.tuples).toMap, description, tester) 70 | } 71 | 72 | class ResultOfGegeven(val context: Context, val description: String, val tester: BerekeningenTester) { 73 | def verwacht(factValues: FactValues*): Unit = tester.runTest(description, context, factValues) 74 | def debug(factValues: FactValues*): Unit = tester.debugTest(description, context, factValues) 75 | } 76 | 77 | 78 | //scalastyle:off object.name 79 | object lijst { 80 | def van(lengte: Int): listIntermediateResult = new listIntermediateResult(lengte) 81 | 82 | class listIntermediateResult(lengte: Int) { 83 | def waarde[A](value: A) : List[A] = List.fill(lengte)(value) 84 | } 85 | } 86 | //scalastyle:on object.name 87 | -------------------------------------------------------------------------------- /engine-test-utils/src/test/scala/org/scalarules/testutils/nl/TestDslTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.testutils.nl 2 | 3 | import org.scalarules.finance.nl._ 4 | import org.scalarules.facts.{ListFact, SingularFact} 5 | import org.scalarules.testutils.nl.TestDslTestGlossary._ 6 | 7 | class TestDslTest extends BerekeningenTester(new TestDslTestBerekening) { 8 | 9 | test("omlaag afronden van Bedrag Per Jaar") gegeven ( 10 | BedragPerJaarA is ("50.123456789".euro per Jaar) 11 | ) verwacht ( 12 | BedragPerJaarB is ("50.12".euro per Jaar) 13 | ) 14 | 15 | test("omhoog afronden van Bedrag Per Jaar") gegeven ( 16 | BedragPerJaarA is ("50.126456789".euro per Jaar) 17 | ) verwacht ( 18 | BedragPerJaarB is ("50.13".euro per Jaar) 19 | ) 20 | 21 | test("niet afronden van Bedrag Per Jaar") gegeven ( 22 | BedragPerJaarA is ("50.12".euro per Jaar) 23 | ) verwacht ( 24 | BedragPerJaarB is ("50.12".euro per Jaar) 25 | ) 26 | 27 | test("lege lijst wordt geïnterpreteerd als value") gegeven ( 28 | LijstInt is List() 29 | ) verwacht ( 30 | LijstInt is List() 31 | ) 32 | 33 | } 34 | 35 | object TestDslTestGlossary { 36 | val BedragPerJaarA: SingularFact[Bedrag Per Jaar] = new SingularFact[Bedrag Per Jaar]("BedragPerJaarA") 37 | val BedragPerJaarB: SingularFact[Bedrag Per Jaar] = new SingularFact[Bedrag Per Jaar]("BedragPerJaarB") 38 | 39 | val LijstInt: ListFact[Int] = new ListFact[Int]("LijstInt") 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /engine-test-utils/src/test/scala/org/scalarules/testutils/nl/TestDslTestBerekening.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.testutils.nl 2 | 3 | import org.scalarules.dsl.nl.grammar._ 4 | import org.scalarules.testutils.nl.TestDslTestGlossary._ 5 | 6 | class TestDslTestBerekening extends Berekening ( 7 | Gegeven(altijd) 8 | Bereken 9 | BedragPerJaarB is BedragPerJaarA 10 | ) 11 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/glossaries/Glossary.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.utils 2 | 3 | import org.scalarules.facts.{Fact, ListFact, SingularFact} 4 | 5 | import scala.language.experimental.macros 6 | 7 | /** 8 | * Utility base class for collecting and namespacing `Fact`s. You can extend this class, define facts in it and receive a 9 | * utility collection of all facts declared in your class. 10 | */ 11 | class Glossary { 12 | /** 13 | * Defines a `Fact`, using the name of the `val` it is assigned to as the name of the `Fact`. Note: the value of this 14 | * macro *must* be assigned to a `val`, otherwise a compiler error will be raised. 15 | * 16 | * @tparam A the value type of the resulting `Fact`. 17 | * @return a `SingularFact` initialized with the name of the val declaration. 18 | */ 19 | def defineFact[A](): SingularFact[A] = macro FactMacros.defineFactMacroImpl[A] 20 | 21 | /** 22 | * Defines a `Fact`, using the name of the `val` it is assigned to as the name of the `Fact`. Note: the value of this 23 | * macro *must* be assigned to a `val`, otherwise a compiler error will be raised. 24 | * 25 | * @tparam A the value type of the resulting `Fact`. 26 | * @param description description of the `Fact`, to be passed along to the `Fact`'s constructor. 27 | * @return a `SingularFact` initialized with the name of the val declaration. 28 | */ 29 | def defineFact[A](description: String): SingularFact[A] = macro FactMacros.defineFactMacroWithDescriptionImpl[A] 30 | 31 | /** 32 | * Defines a `Fact`, using the name of the `val` it is assigned to as the name of the `Fact`. Note: the value of this 33 | * macro *must* be assigned to a `val`, otherwise a compiler error will be raised. 34 | * 35 | * @tparam A the value type of the resulting `Fact`. 36 | * @return a `ListFact` initialized with the name of the val declaration. 37 | */ 38 | def defineListFact[A](): ListFact[A] = macro FactMacros.defineListFactMacroImpl[A] 39 | 40 | /** 41 | * Defines a `Fact`, using the name of the `val` it is assigned to as the name of the `Fact`. Note: the value of this 42 | * macro *must* be assigned to a `val`, otherwise a compiler error will be raised. 43 | * 44 | * @tparam A the value type of the resulting `Fact`. 45 | * @param description description of the `Fact`, to be passed along to the `Fact`'s constructor. 46 | * @return a `ListFact` initialized with the name of the val declaration. 47 | */ 48 | def defineListFact[A](description: String): ListFact[A] = macro FactMacros.defineListFactMacroWithDescriptionImpl[A] 49 | 50 | /** 51 | * Collects all declared `Fact`s in this `Glossary` and returns them mapped from their names to their definitions. 52 | */ 53 | lazy val facts: Map[String, Fact[Any]] = { 54 | this.getClass.getDeclaredFields 55 | .filter( field => classOf[Fact[Any]].isAssignableFrom( field.getType ) ) 56 | .map( field => { 57 | field.setAccessible(true) 58 | val fact: Fact[Any] = field.get(this).asInstanceOf[Fact[Any]] 59 | (fact.name, fact) 60 | }) 61 | .toMap 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/operators/Addable.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.operators 2 | 3 | import org.scalarules.dsl.core.types.AddableValues 4 | 5 | import scala.annotation.implicitNotFound 6 | 7 | /** 8 | * This type class allows values of different types to be added in the DSL. 9 | * 10 | * @tparam A type of the left hand side of the adding operation 11 | * @tparam B type of the right hand side of the adding operation 12 | * @tparam C type of the result of the adding operation 13 | */ 14 | @implicitNotFound("No member of type class Addable available in scope for combination ${A} + ${B} = ${C}") 15 | sealed trait Addable[A, B, C] extends BinaryOperable[A, B, C] { 16 | def operation(a: A, b: B): C 17 | 18 | def identityLeft: A 19 | def identityRight: B 20 | 21 | def representation: String = "+" 22 | } 23 | 24 | object Addable { 25 | implicit def valueAddedToValue[A, B, C](implicit ev: AddableValues[A, B, C]): Addable[A, B, C] = new Addable[A, B, C] { 26 | override def operation(n: A, m: B): C = ev.plus(n, m) 27 | override def identityLeft = ev.leftUnit 28 | override def identityRight = ev.rightUnit 29 | } 30 | implicit def listAddedToList[A, B, C](implicit ev: AddableValues[A, B, C]): Addable[List[A], List[B], List[C]] = new Addable[List[A], List[B], List[C]] { 31 | override def operation(n: List[A], m: List[B]): List[C] = n.zipAll(m, ev.leftUnit, ev.rightUnit).map(t => ev.plus(t._1, t._2)) 32 | override def identityLeft = List(ev.leftUnit) 33 | override def identityRight = List(ev.rightUnit) 34 | } 35 | implicit def listAddedToValue[A, B, C](implicit ev: AddableValues[A, B, C]): Addable[List[A], B, List[C]] = new Addable[List[A], B, List[C]] { 36 | override def operation(n: List[A], m: B): List[C] = n.map( ev.plus(_, m) ) 37 | override def identityLeft = List(ev.leftUnit) 38 | override def identityRight = ev.rightUnit 39 | } 40 | implicit def valueAddedToList[A, B, C](implicit ev: AddableValues[A, B, C]): Addable[A, List[B], List[C]] = new Addable[A, List[B], List[C]] { 41 | override def operation(n: A, m: List[B]): List[C] = m.map( ev.plus(n, _) ) 42 | override def identityLeft = ev.leftUnit 43 | override def identityRight = List(ev.rightUnit) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/operators/BinaryOperable.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.operators 2 | 3 | trait BinaryOperable[A, B, C] { 4 | def operation(a: A, b: B): C 5 | 6 | def identityLeft: A 7 | def identityRight: B 8 | 9 | def representation: String 10 | } 11 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/operators/Divisible.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.operators 2 | 3 | import org.scalarules.dsl.core.types.DivisibleValues 4 | 5 | import scala.annotation.implicitNotFound 6 | 7 | /** 8 | * This type class allows values of different types to be added in the DSL. 9 | * 10 | * @tparam A type of the left hand side of the adding operation 11 | * @tparam B type of the right hand side of the adding operation 12 | * @tparam C type of the result of the adding operation 13 | */ 14 | @implicitNotFound("No member of type class Divisible available in scope for combination ${A} / ${B} = ${C}") 15 | sealed trait Divisible[A, B, C] extends BinaryOperable[A, B, C] { 16 | def operation(a: A, b: B): C 17 | 18 | def identityLeft: A 19 | def identityRight: B 20 | 21 | def representation: String = "/" 22 | } 23 | 24 | object Divisible { 25 | implicit def valueDividedByValue[A, B, C](implicit ev: DivisibleValues[A, B, C]): Divisible[A, B, C] = new Divisible[A, B, C] { 26 | override def operation(n: A, m: B): C = ev.divide(n, m) 27 | override def identityLeft = ev.leftUnit 28 | override def identityRight = ev.rightUnit 29 | } 30 | implicit def listDividedByList[A, B, C](implicit ev: DivisibleValues[A, B, C]): Divisible[List[A], List[B], List[C]] = new Divisible[List[A], List[B], List[C]] { 31 | override def operation(n: List[A], m: List[B]): List[C] = n.zip(m).map(t => ev.divide(t._1, t._2)) 32 | override def identityLeft = List(ev.leftUnit) 33 | override def identityRight = List(ev.rightUnit) 34 | } 35 | implicit def listDividedByValue[A, B, C](implicit ev: DivisibleValues[A, B, C]): Divisible[List[A], B, List[C]] = new Divisible[List[A], B, List[C]] { 36 | override def operation(n: List[A], m: B): List[C] = n.map( ev.divide(_, m) ) 37 | override def identityLeft = List(ev.leftUnit) 38 | override def identityRight = ev.rightUnit 39 | } 40 | implicit def valueDividedByList[A, B, C](implicit ev: DivisibleValues[A, B, C]): Divisible[A, List[B], List[C]] = new Divisible[A, List[B], List[C]] { 41 | override def operation(n: A, m: List[B]): List[C] = m.map( ev.divide(n, _) ) 42 | override def identityLeft = ev.leftUnit 43 | override def identityRight = List(ev.rightUnit) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/operators/Multipliable.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.operators 2 | 3 | import org.scalarules.dsl.core.types.MultipliableValues 4 | 5 | import scala.annotation.implicitNotFound 6 | 7 | /** 8 | * This type class allows values of different types to be added in the DSL. 9 | * 10 | * @tparam A type of the left hand side of the adding operation 11 | * @tparam B type of the right hand side of the adding operation 12 | * @tparam C type of the result of the adding operation 13 | */ 14 | @implicitNotFound("No member of type class Multipliabe available in scope for combination ${A} * ${B} = ${C}") 15 | sealed trait Multipliable[A, B, C] extends BinaryOperable[A, B, C] { 16 | def operation(a: A, b: B): C 17 | 18 | def identityLeft: A 19 | def identityRight: B 20 | 21 | def representation: String = "*" 22 | } 23 | 24 | object Multipliable { 25 | implicit def valueMultipliedByValue[A, B, C](implicit ev: MultipliableValues[A, B, C]): Multipliable[A, B, C] = new Multipliable[A, B, C] { 26 | override def operation(n: A, m: B): C = ev.multiply(n, m) 27 | override def identityLeft = ev.leftUnit 28 | override def identityRight = ev.rightUnit 29 | } 30 | implicit def listMultipliedByList[A, B, C](implicit ev: MultipliableValues[A, B, C]): Multipliable[List[A], List[B], List[C]] = new Multipliable[List[A], List[B], List[C]] { 31 | override def operation(n: List[A], m: List[B]): List[C] = n.zip(m).map(t => ev.multiply(t._1, t._2)) 32 | override def identityLeft = List(ev.leftUnit) 33 | override def identityRight = List(ev.rightUnit) 34 | } 35 | implicit def listMultipliedByValue[A, B, C](implicit ev: MultipliableValues[A, B, C]): Multipliable[List[A], B, List[C]] = new Multipliable[List[A], B, List[C]] { 36 | override def operation(n: List[A], m: B): List[C] = n.map( ev.multiply(_, m) ) 37 | override def identityLeft = List(ev.leftUnit) 38 | override def identityRight = ev.rightUnit 39 | } 40 | implicit def valueMultipliedByList[A, B, C](implicit ev: MultipliableValues[A, B, C]): Multipliable[A, List[B], List[C]] = new Multipliable[A, List[B], List[C]] { 41 | override def operation(n: A, m: List[B]): List[C] = m.map( ev.multiply(n, _) ) 42 | override def identityLeft = ev.leftUnit 43 | override def identityRight = List(ev.rightUnit) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/operators/Subtractable.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.operators 2 | 3 | import org.scalarules.dsl.core.types.SubtractableValues 4 | 5 | import scala.annotation.implicitNotFound 6 | 7 | /** 8 | * This type class allows values of different types to be added in the DSL. 9 | * 10 | * @tparam A type of the left hand side of the adding operation 11 | * @tparam B type of the right hand side of the adding operation 12 | * @tparam C type of the result of the adding operation 13 | */ 14 | @implicitNotFound("No member of type class Subtractable available in scope for combination ${A} - ${B} = ${C}") 15 | trait Subtractable[A, B, C] extends BinaryOperable[A, B, C] { 16 | def operation(a: A, b: B): C 17 | 18 | def identityLeft: A 19 | def identityRight: B 20 | 21 | def representation: String = "-" 22 | } 23 | 24 | object Subtractable { 25 | implicit def valueSubtractedByValue[A, B, C](implicit ev: SubtractableValues[A, B, C]): Subtractable[A, B, C] = new Subtractable[A, B, C] { 26 | override def operation(n: A, m: B): C = ev.minus(n, m) 27 | override def identityLeft = ev.leftUnit 28 | override def identityRight = ev.rightUnit 29 | } 30 | implicit def listSubtractedByList[A, B, C](implicit ev: SubtractableValues[A, B, C]): Subtractable[List[A], List[B], List[C]] = new Subtractable[List[A], List[B], List[C]] { 31 | override def operation(n: List[A], m: List[B]): List[C] = n.zipAll(m, ev.leftUnit, ev.rightUnit).map(t => ev.minus(t._1, t._2)) 32 | override def identityLeft = List(ev.leftUnit) 33 | override def identityRight = List(ev.rightUnit) 34 | } 35 | implicit def listSubtractedByValue[A, B, C](implicit ev: SubtractableValues[A, B, C]): Subtractable[List[A], B, List[C]] = new Subtractable[List[A], B, List[C]] { 36 | override def operation(n: List[A], m: B): List[C] = n.map( ev.minus(_, m) ) 37 | override def identityLeft = List(ev.leftUnit) 38 | override def identityRight = ev.rightUnit 39 | } 40 | implicit def valueSubtractedByList[A, B, C](implicit ev: SubtractableValues[A, B, C]): Subtractable[A, List[B], List[C]] = new Subtractable[A, List[B], List[C]] { 41 | override def operation(n: A, m: List[B]): List[C] = m.map( ev.minus(n, _) ) 42 | override def identityLeft = ev.leftUnit 43 | override def identityRight = List(ev.rightUnit) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/projections/ProjectableFields.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.projections 2 | 3 | import org.scalarules.dsl.nl.grammar.{DslCondition, DslEvaluation} 4 | import org.scalarules.engine._ 5 | import org.scalarules.facts.{Fact, ListFact} 6 | 7 | /** 8 | * Domain objects in the DSL can allow access to their fields through projections. 9 | * This trait signals the support of field projections and provides a convenience 10 | * method to create these projections. 11 | * 12 | * Here's an example: 13 | * 14 | * {{{ 15 | * case class Person(val name: String) 16 | * 17 | * class PersonFieldProjections(personFact: SingularFact[Person]) extends ProjectableFields[Person] { 18 | * def outerFact: Fact[Person] = personFact 19 | * 20 | * def name: DslEvaluation[String] = projectField( _.name ) 21 | * } 22 | * 23 | * object PersonImplicits { 24 | * implicit def toPersonFieldProjections(personFact: SingularFact[Person]): PersonFieldProjections = new PersonFieldProjections(personFact) 25 | * } 26 | * }}} 27 | * 28 | * With these elements in place, you can import the PersonImplicits._ where you want to use it in your 29 | * DSL and you can refer to the `name` field of any `Fact` of type `Person`. 30 | * 31 | * @tparam C type from which the field(s) can be projected. 32 | * @author Jan-Hendrik Kuperus (jan-hendrik@scala-rules.org) 33 | */ 34 | trait ProjectableFields[C] { 35 | 36 | /** 37 | * Any implementing class should provide the fact from which to project the fields through this method. 38 | * 39 | * @return the Fact of type C from which fields will be projected. 40 | */ 41 | protected def outerFact: Fact[C] 42 | 43 | /** 44 | * Provides a utility method to construct the DslEvaluation which entails the field projection. 45 | * 46 | * @param f the function which projects the Fact's value to the corresponding field of type F. 47 | * @tparam F type of the projected field, which will also be the type of the resulting DslEvaluation. 48 | * @return a DslEvaluation of the same type as the projected field. This evaluation will at runtime 49 | * provide the value of the projected field of the accompanying Fact. 50 | */ 51 | protected def projectField[F](f: C => F): DslEvaluation[F] = DslEvaluation( 52 | DslCondition.factFilledCondition(outerFact), 53 | new ProjectionEvaluation[C, F](new SingularFactEvaluation[C](outerFact), f) 54 | ) 55 | 56 | } 57 | 58 | /** 59 | * Domain objects in the DSL can allow access to their fields through projections. 60 | * This trait signals the support of field projections and provides a convenience 61 | * method to create these projections. This trait is meant for Lists of objects to project 62 | * a List of traits back. 63 | * 64 | * Here's an example: 65 | * 66 | * {{{ 67 | * case class Person(val name: String) 68 | * 69 | * class PersonFieldListProjections(personFact: ListFact[Person]) extends ProjectableListFields[Person] { 70 | * def outerFact: Fact[Person] = personFact 71 | * 72 | * def name: DslEvaluation[String] = projectField( _.name ) 73 | * } 74 | * 75 | * object PersonImplicits { 76 | * implicit def toPersonFieldListProjections(personFact: ListFact[Person]): PersonListFieldProjections = new PersonListFieldProjections(personFact) 77 | * } 78 | * }}} 79 | * 80 | * With these elements in place, you can import the PersonImplicits._ where you want to use it in your 81 | * DSL and you can refer to the `name` field of any `Fact` of type `Person`. 82 | * 83 | * @tparam C type from which the field(s) can be projected. 84 | * @author Vincent Zorge (vincent@scala-rules.org) 85 | */ 86 | trait ProjectableListFields[C] { 87 | 88 | /** 89 | * Any implementing class should provide the fact from which to project the fields through this method. 90 | * 91 | * @return the ListFact of type C from which fields will be projected. 92 | */ 93 | protected def outerFact: ListFact[C] 94 | 95 | /** 96 | * Provides a utility method to construct the DslEvaluation which entails the field projection. 97 | * 98 | * @param f the function which projects the Fact's value to the corresponding field of type F. 99 | * @tparam F type of the projected field, which will also be the type of the resulting DslEvaluation. 100 | * @return a DslEvaluation of the same type as the projected field. This evaluation will at runtime 101 | * provide the value of the projected field of the accompanying Fact. 102 | */ 103 | protected def projectField[F](f: C => F): ProjectedDslEvaluation[C, F] = new ProjectedDslEvaluation(f, 104 | DslCondition.factFilledCondition(outerFact), 105 | new ProjectionListEvaluation[C, F](outerFact.toEval, f) 106 | ) 107 | } 108 | 109 | class ProjectedDslEvaluation[C, F](val transform: C => F, condition: DslCondition, evaluation: Evaluation[List[F]]) extends DslEvaluation[List[F]](condition, evaluation) 110 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/temporal/LocalDate.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.temporal 2 | 3 | import org.joda.time.{LocalDate => JodaLocalDate} 4 | 5 | /** 6 | * Provides the DSL with a Date type. It supports the Ordered trait, which means it can be used 7 | * in DslConditions with the comparative operators. 8 | * 9 | * @param internal internal representation of the date. 10 | */ 11 | case class LocalDate private[temporal](internal: JodaLocalDate) extends Ordered[LocalDate] { 12 | 13 | override def compare(that: LocalDate): Int = internal.compareTo(that.internal) 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/types/AddableValues.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.types 2 | 3 | import org.scalarules.finance.core.Quantity 4 | 5 | import scala.annotation.implicitNotFound 6 | 7 | /** 8 | * This type class allows values of different types to be added in the DSL. 9 | * 10 | * @tparam A type of the left hand side of the adding multiply 11 | * @tparam B type of the right hand side of the adding multiply 12 | * @tparam C type of the result of the adding multiply 13 | */ 14 | @implicitNotFound("No member of type class AddableValue available in scope for combination ${A} + ${B} = ${C}") 15 | trait AddableValues[A, B, C] { 16 | def plus(a: A, b: B): C 17 | 18 | def leftUnit: A 19 | def rightUnit: B 20 | } 21 | 22 | object AddableValues { 23 | implicit def bigDecimalAddedToBigDecimal: AddableValues[BigDecimal, BigDecimal, BigDecimal] = new AddableValues[BigDecimal, BigDecimal, BigDecimal] { 24 | override def plus(a: BigDecimal, b: BigDecimal): BigDecimal = a + b 25 | override def leftUnit: BigDecimal = 0 26 | override def rightUnit: BigDecimal = 0 27 | } 28 | implicit def intAddedToInt: AddableValues[Int, Int, Int] = new AddableValues[Int, Int, Int] { 29 | override def plus(a: Int, b: Int): Int = a + b 30 | override def leftUnit: Int = 0 31 | override def rightUnit: Int = 0 32 | } 33 | implicit def quantityAddedToQuantity[N : Quantity]: AddableValues[N, N, N] = new AddableValues[N, N, N] { 34 | private val ev = implicitly[Quantity[N]] 35 | override def plus(a: N, b: N): N = ev.plus(a, b) 36 | override def leftUnit: N = ev.zero 37 | override def rightUnit: N = ev.zero 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/types/DivisibleValues.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.types 2 | 3 | import org.scalarules.finance.core.Quantity 4 | import org.scalarules.finance.nl._ 5 | 6 | import scala.annotation.implicitNotFound 7 | 8 | /** 9 | * This type class allows values of different types to be added in the DSL. 10 | * 11 | * @tparam A type of the left hand side of the adding multiply 12 | * @tparam B type of the right hand side of the adding multiply 13 | * @tparam C type of the result of the adding multiply 14 | */ 15 | @implicitNotFound("No member of type class DivisibleValues available in scope for combination ${A} / ${B} = ${C}") 16 | trait DivisibleValues[A, B, C] { 17 | def divide(a: A, b: B): C 18 | 19 | def leftUnit: A 20 | def rightUnit: B 21 | } 22 | 23 | object DivisibleValues { 24 | implicit def bigDecimalDividedByBigDecimal: DivisibleValues[BigDecimal, BigDecimal, BigDecimal] = new DivisibleValues[BigDecimal, BigDecimal, BigDecimal] { 25 | override def divide(a: BigDecimal, b: BigDecimal): BigDecimal = a / b 26 | override def leftUnit: BigDecimal = 0 27 | override def rightUnit: BigDecimal = 1 28 | } 29 | 30 | implicit def somethingDividedByBigDecimal[N : Quantity]: DivisibleValues[N, BigDecimal, N] = new DivisibleValues[N, BigDecimal, N] { 31 | private val ev = implicitly[Quantity[N]] 32 | override def divide(a: N, b: BigDecimal): N = ev.divide(a, b) 33 | override def leftUnit: N = ev.zero 34 | override def rightUnit: BigDecimal = 1 35 | } 36 | 37 | implicit def somethingDividedByPercentage[N : Quantity]: DivisibleValues[N, Percentage, N] = new DivisibleValues[N, Percentage, N] { 38 | private val ev = implicitly[Quantity[N]] 39 | override def divide(a: N, b: Percentage): N = ev.divide(a, b.alsFractie) 40 | override def leftUnit: N = ev.zero 41 | override def rightUnit: Percentage = 100.procent 42 | } 43 | 44 | implicit def somethingDividedByInt[N : Quantity]: DivisibleValues[N, Int, N] = new DivisibleValues[N, Int, N] { 45 | // Currently BigDecimal gets wrapped in a NumberLike, which is why this will also work for BigDecimal. 46 | private val ev = implicitly[Quantity[N]] 47 | override def divide(a: N, b: Int): N = ev.divide(a, b) 48 | override def leftUnit: N = ev.zero 49 | override def rightUnit: Int = 1 50 | } 51 | 52 | implicit def percentageDividedByBigDecimal: DivisibleValues[Percentage, BigDecimal, BigDecimal] = new DivisibleValues[Percentage, BigDecimal, BigDecimal] { 53 | override def divide(a: Percentage, b: BigDecimal): BigDecimal = a / b 54 | override def leftUnit: Percentage = 0.procent 55 | override def rightUnit: BigDecimal = 1 56 | } 57 | 58 | implicit def somethingDividedBySomethingAsPercentage: DivisibleValues[Bedrag, Bedrag, Percentage] = new DivisibleValues[Bedrag, Bedrag, Percentage] { 59 | // Note: this type class instance was initially as Quantity x Quantity => Percentage, but the QuantityBigDecimal throws a fit if we do that 60 | // and makes it ambiguous to choose when trying to divide two BigDecimals 61 | // private val ev = implicitly[Quantity[Bedrag]] 62 | override def divide(a: Bedrag, b: Bedrag): Percentage = (a.waarde / b.waarde * 100).procent 63 | override def leftUnit: Bedrag = 0.euro 64 | override def rightUnit: Bedrag = 1.euro 65 | } 66 | 67 | // Note: there is no somethingDividedByBedrag, because the division is not a commutative operation 68 | // and dividing a BigDecimal by something like a Bedrag would yield a BigDecimal Per Bedrag type, which 69 | // I do not yet foresee any use for. 70 | } 71 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/types/MultipliableValues.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.types 2 | 3 | import org.scalarules.finance.core.Quantity 4 | import org.scalarules.finance.nl._ 5 | 6 | import scala.annotation.implicitNotFound 7 | 8 | /** 9 | * This type class allows values of different types to be added in the DSL. 10 | * 11 | * @tparam A type of the left hand side of the adding multiply 12 | * @tparam B type of the right hand side of the adding multiply 13 | * @tparam C type of the result of the adding multiply 14 | */ 15 | @implicitNotFound("No member of type class MultipliableValues available in scope for combination ${A} * ${B} = ${C}") 16 | trait MultipliableValues[A, B, C] { 17 | def multiply(a: A, b: B): C 18 | 19 | def leftUnit: A 20 | def rightUnit: B 21 | } 22 | 23 | object MultipliableValues { 24 | implicit def bigDecimalTimesBigDecimal: MultipliableValues[BigDecimal, BigDecimal, BigDecimal] = new MultipliableValues[BigDecimal, BigDecimal, BigDecimal] { 25 | override def multiply(a: BigDecimal, b: BigDecimal): BigDecimal = a * b 26 | override def leftUnit: BigDecimal = 0 27 | override def rightUnit: BigDecimal = 0 28 | } 29 | implicit def somethingTimesBigDecimal[N : Quantity]: MultipliableValues[N, BigDecimal, N] = new MultipliableValues[N, BigDecimal, N] { 30 | private val ev = implicitly[Quantity[N]] 31 | override def multiply(a: N, b: BigDecimal): N = ev.multiply(a, b) 32 | override def leftUnit: N = ev.zero 33 | override def rightUnit: BigDecimal = 0 34 | } 35 | implicit def bigDecimalTimesSomething[N : Quantity]: MultipliableValues[BigDecimal, N, N] = new MultipliableValues[BigDecimal, N, N] { 36 | private val ev = implicitly[Quantity[N]] 37 | override def multiply(a: BigDecimal, b: N): N = ev.multiply(b, a) 38 | override def leftUnit: BigDecimal = 0 39 | override def rightUnit: N = ev.zero 40 | } 41 | implicit def quantityTimesPercentage[N : Quantity]: MultipliableValues[N, Percentage, N] = new MultipliableValues[N, Percentage, N] { 42 | private val ev = implicitly[Quantity[N]] 43 | override def multiply(a: N, b: Percentage): N = b * a 44 | override def leftUnit: N = ev.zero 45 | override def rightUnit: Percentage = 0.procent 46 | } 47 | implicit def percentageTimesQuantity[N : Quantity]: MultipliableValues[Percentage, N, N] = new MultipliableValues[Percentage, N, N] { 48 | private val ev = implicitly[Quantity[N]] 49 | override def multiply(a: Percentage, b: N): N = a * b 50 | override def leftUnit: Percentage = 0.procent 51 | override def rightUnit: N = ev.zero 52 | } 53 | implicit def bedragTimesPeriode: MultipliableValues[Bedrag, Periode, Bedrag] = new MultipliableValues[Bedrag, Periode, Bedrag] { 54 | override def multiply(a: Bedrag, b: Periode): Bedrag = a * b.inMaanden 55 | override def leftUnit: Bedrag = 0.euro 56 | override def rightUnit: Periode = 0.maanden 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/core/types/SubtractableValues.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.types 2 | 3 | import org.scalarules.finance.core.Quantity 4 | 5 | import scala.annotation.implicitNotFound 6 | 7 | /** 8 | * This type class allows values of different types to be added in the DSL. 9 | * 10 | * @tparam A type of the left hand side of the adding multiply 11 | * @tparam B type of the right hand side of the adding multiply 12 | * @tparam C type of the result of the adding multiply 13 | */ 14 | @implicitNotFound("No member of type class SubtractableValues available in scope for combination ${A} - ${B} = ${C}") 15 | trait SubtractableValues[A, B, C] { 16 | def minus(a: A, b: B): C 17 | 18 | def leftUnit: A 19 | def rightUnit: B 20 | } 21 | 22 | object SubtractableValues { 23 | implicit def bigDecimalSubtractedByBigDecimal: SubtractableValues[BigDecimal, BigDecimal, BigDecimal] = new SubtractableValues[BigDecimal, BigDecimal, BigDecimal] { 24 | override def minus(a: BigDecimal, b: BigDecimal): BigDecimal = a - b 25 | override def leftUnit: BigDecimal = 0 26 | override def rightUnit: BigDecimal = 0 27 | } 28 | implicit def quantitySubtractedByQuantity[N : Quantity]: SubtractableValues[N, N, N] = new SubtractableValues[N, N, N] { 29 | private val ev = implicitly[Quantity[N]] 30 | override def minus(a: N, b: N): N = ev.minus(a, b) 31 | override def leftUnit: N = ev.zero 32 | override def rightUnit: N = ev.zero 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/ScalaRulesDsl.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl 2 | 3 | import org.scalarules.dsl.nl.datum.DatumImplicits 4 | import org.scalarules.dsl.nl.grammar._ 5 | import org.scalarules.dsl.nl.grammar.meta.DslMacros 6 | import org.scalarules.engine._ 7 | 8 | import scala.language.experimental.macros 9 | 10 | /** 11 | * Aggregates the keywords and implicit definitions of the Scala-Rules DSL. The implicits available in this 12 | * trait can be used by importing the `grammar` package object's members, or extending this trait. 13 | */ 14 | trait ScalaRulesDsl extends AfrondingsWordsTrait 15 | with DslConditionImplicits 16 | with DslEvaluationImplicits 17 | with DatumImplicits 18 | with DslListFilterWord 19 | { 20 | 21 | type ConditionFunction = (Condition, Condition) => Condition 22 | 23 | // Entrypoint for the DSL 24 | def Gegeven(condition: DslCondition): GegevenWord = macro DslMacros.captureGegevenSourcePositionMacroImpl //scalastyle:ignore method.name 25 | 26 | val resultaten = new ResultatenWord 27 | 28 | val Invoer = new InvoerWord 29 | val Uitvoer = new UitvoerWord 30 | } 31 | 32 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/datum/DatumImplicits.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.datum 2 | 3 | import java.util.Date 4 | 5 | import org.joda.time.{LocalDate => JodaLocalDate} 6 | import org.joda.time.format.DateTimeFormat 7 | import org.scalarules.dsl.core.temporal.LocalDate 8 | 9 | /** 10 | * Provides the implicit conversions to DslDate for the NL version of the DSL. 11 | * It defines a Dutch type alias `Datum` for `org.scalarules.dsl.core.temporal.LocalDate`. 12 | * 13 | * Currently supported conversions are: 14 | * - `String` (formatted as dd-MM-yyyy) 15 | * - JodaTime `org.joda.time.LocalDate` 16 | * - `java.util.Date` 17 | */ 18 | trait DatumImplicits { 19 | 20 | type Datum = LocalDate 21 | 22 | abstract class ToDslDate(internal: JodaLocalDate) { 23 | /** Builds DslDate from the provided value. */ 24 | def datum: Datum = LocalDate(internal) 25 | 26 | } 27 | 28 | lazy val dtf = DateTimeFormat.forPattern("dd-MM-yyyy") 29 | 30 | implicit class JodaLocalDateToDslDate(external: JodaLocalDate) extends ToDslDate(external) 31 | implicit class JavaDateToDslDate(external: Date) extends ToDslDate(new JodaLocalDate(external)) 32 | implicit class StringToDslDate(external: String) extends ToDslDate(dtf.parseLocalDate(external)) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/datum/package.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl 2 | 3 | package object datum extends DatumImplicits { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/AfrondingsWordsTrait.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.dsl.nl.grammar.DslCondition._ 4 | import org.scalarules.engine._ 5 | import org.scalarules.facts.Fact 6 | import org.scalarules.finance.nl.{Bedrag, Percentage} 7 | 8 | import scala.language.{implicitConversions, postfixOps} 9 | import scala.math.BigDecimal.RoundingMode.RoundingMode 10 | 11 | trait AfrondingsWordsTrait { 12 | 13 | /** 14 | * a "filler" value required to enforce proper Dsl readability 15 | */ 16 | val afgerond = new AfgerondKeyword 17 | 18 | implicit class AfrondingsTerm[T: Afrondbaar](afTeRonden: Fact[T]) { 19 | 20 | /** 21 | * initiates a dsl mathematical rounding sequence: half-even 22 | * 23 | * @param afgerond obligatory word-val to enable a natural language expression of rounding 24 | * @return AfrondingOpWord 25 | */ 26 | def halfNaarEven(afgerond: AfgerondKeyword): AfrondingOpWord[T] = new AfrondingOpWord[T](afTeRonden, BigDecimal.RoundingMode.HALF_EVEN) 27 | 28 | /** 29 | * initiates a dsl mathematical rounding sequence: half-down 30 | * 31 | * @param afgerond obligatory word-val to enable a natural language expression of rounding 32 | * @return AfrondingOpWord 33 | */ 34 | def halfNaarNulToe(afgerond: AfgerondKeyword): AfrondingOpWord[T] = new AfrondingOpWord[T](afTeRonden, BigDecimal.RoundingMode.HALF_DOWN) 35 | 36 | /** 37 | * initiates a dsl mathematical rounding sequence: floor 38 | * 39 | * @param afgerond obligatory word-val to enable a natural language expression of rounding 40 | * @return AfrondingOpWord 41 | */ 42 | def naarBeneden(afgerond: AfgerondKeyword): AfrondingOpWord[T] = new AfrondingOpWord[T](afTeRonden, BigDecimal.RoundingMode.FLOOR) 43 | 44 | /** 45 | * initiates a dsl mathematical rounding sequence: ceiling 46 | * 47 | * @param afgerond obligatory word-val to enable a natural language expression of rounding 48 | * @return AfrondingOpWord 49 | */ 50 | def naarBoven(afgerond: AfgerondKeyword): AfrondingOpWord[T] = new AfrondingOpWord[T](afTeRonden, BigDecimal.RoundingMode.CEILING) 51 | 52 | /** 53 | * initiates a dsl mathematical rounding sequence: down 54 | * 55 | * @param afgerond obligatory word-val to enable a natural language expression of rounding 56 | * @return AfrondingOpWord 57 | */ 58 | def naarNulToe(afgerond: AfgerondKeyword): AfrondingOpWord[T] = new AfrondingOpWord[T](afTeRonden, BigDecimal.RoundingMode.DOWN) 59 | 60 | /** 61 | * initiates a dsl mathematical rounding sequence: half-up 62 | * 63 | * @param afgerond obligatory word-val to enable a natural language expression of rounding 64 | * @return AfrondingOpWord 65 | */ 66 | def rekenkundig(afgerond: AfgerondKeyword): AfrondingOpWord[T] = new AfrondingOpWord[T](afTeRonden, BigDecimal.RoundingMode.HALF_UP) 67 | 68 | /** 69 | * initiates a dsl mathematical rounding sequence: up 70 | * 71 | * @param afgerond obligatory word-val to enable a natural language expression of rounding 72 | * @return AfrondingOpWord 73 | */ 74 | def vanNulAf(afgerond: AfgerondKeyword): AfrondingOpWord[T] = new AfrondingOpWord[T](afTeRonden, BigDecimal.RoundingMode.UP) 75 | } 76 | 77 | class AfrondingOpWord[T: Afrondbaar](afTeRonden: Fact[T], roundingMode: RoundingMode) { 78 | 79 | /** 80 | * allows the dsl rounding to take an integer as its parameter for the amount of digits to be rounded to 81 | * 82 | * @param aantalDecimalen integer representing the number of digits to be rounded to 83 | * @return Afronding 84 | */ 85 | def op(aantalDecimalen: Integer): Afronding[T] = new Afronding[T](afTeRonden, aantalDecimalen, roundingMode) 86 | 87 | } 88 | 89 | class Afronding[T: Afrondbaar](afTeRonden: Fact[T], aantalDecimalen: Integer, roundingMode: RoundingMode) { 90 | /** 91 | * We could have ended the call with "op" (where we have all the information), 92 | * but for natural language purposes and later flexibility, were are enforcing "decimalen" as closing statement. 93 | * 94 | * @return DslEvaluation[Percentage] 95 | */ 96 | def decimalen: DslEvaluation[T] = { 97 | DslEvaluation(factFilledCondition(afTeRonden), new Evaluation[T] { 98 | override def apply(c: Context): Option[T] = { 99 | val ev = implicitly[Afrondbaar[T]] 100 | Some(ev.rondAfOp(afTeRonden.toEval(c).get, aantalDecimalen, roundingMode)) 101 | } 102 | }) 103 | } 104 | } 105 | 106 | sealed class AfgerondKeyword 107 | 108 | implicit def afrondbaarBigDecimal: Afrondbaar[BigDecimal] = { 109 | new Afrondbaar[BigDecimal] { 110 | override def rondAfOp(afTeRonden: BigDecimal, aantalDecimalen: Integer, afrondingsWijze: RoundingMode): BigDecimal = 111 | afTeRonden.setScale(aantalDecimalen, afrondingsWijze) 112 | } 113 | } 114 | 115 | implicit def afrondbaarPercentage: Afrondbaar[Percentage] = { 116 | new Afrondbaar[Percentage] { 117 | override def rondAfOp(afTeRonden: Percentage, aantalDecimalen: Integer, afrondingsWijze: RoundingMode): Percentage = 118 | afTeRonden.afgerondOp(aantalDecimalen, afrondingsWijze) 119 | } 120 | } 121 | 122 | implicit def afrondbaarBedrag: Afrondbaar[Bedrag] = { 123 | new Afrondbaar[Bedrag] { 124 | override def rondAfOp(afTeRonden: Bedrag, aantalDecimalen: Integer, afrondingsWijze: RoundingMode): Bedrag = 125 | afTeRonden.afgerondOp(aantalDecimalen, afrondingsWijze) 126 | } 127 | } 128 | } 129 | 130 | trait Afrondbaar[T] { 131 | def rondAfOp(afTeRonden: T, aantalDecimalen: Integer, afrondingsWijze: RoundingMode): T 132 | } 133 | 134 | 135 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/BerekeningFlow.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.derivations.{DefaultDerivation, Derivation, SubRunData, SubRunDerivation} 4 | import org.scalarules.dsl.nl.grammar.DslCondition.{andCombineConditions, factFilledCondition} 5 | import org.scalarules.dsl.nl.grammar.meta.DslMacros 6 | import org.scalarules.engine 7 | import org.scalarules.facts.{Fact, ListFact, SingularFact} 8 | import org.scalarules.utils.{FileSourcePosition, SourcePosition, SourceUnknown} 9 | 10 | import scala.language.experimental.macros 11 | 12 | //scalastyle:off method.name 13 | 14 | object Specificatie { 15 | def apply[T](dslCondition: DslCondition, output: Fact[T], dslEvaluation: DslEvaluation[T], sourcePosition: SourcePosition): Derivation = { 16 | val condition = andCombineConditions(dslCondition, dslEvaluation.condition).condition 17 | val input = dslCondition.facts.toList ++ dslEvaluation.condition.facts 18 | 19 | DefaultDerivation(input, output, condition, dslEvaluation.evaluation, sourcePosition, dslCondition.sourcePosition) 20 | } 21 | } 22 | 23 | /* *********************************************************************************************************************************************************** * 24 | * The DSL syntax follows these rules: 25 | * 26 | * Derivation ::= `Gegeven` `(` Condition `)` `Bereken` (SingularBerekenStart | ListBerekenStart) 27 | * 28 | * Condition ::= Fact `is` (Value | Fact | Aanwezig) [en | of] 29 | * 30 | * SingularBerekenStart ::= Fact `is` DslEvaluation 31 | * ListBerekenStart ::= Fact `is` (DslListEvaluation | SubRunData) 32 | * 33 | * DslListAggregationOperation ::= (DslNumericalListAggregator) DslListEvaluation 34 | * DslNumericalListAggregator ::= (`totaal van` | `gemiddelde van`) 35 | * 36 | * DslEvaluation 37 | * LijstOpbouwConstruct ::= [Fact] `bevat` `resultaten` `van` [ElementBerekening] `over` [Fact] 38 | */ 39 | 40 | class GegevenWord(val initialCondition: DslCondition, val position: SourcePosition = SourceUnknown()) { 41 | val condition: DslCondition = position match { 42 | case SourceUnknown() => initialCondition 43 | case fsp @ FileSourcePosition(_, _, _, _, _) => { 44 | val DslCondition(facts, condition, _) = initialCondition 45 | 46 | DslCondition(facts, condition, position) 47 | } 48 | } 49 | 50 | def Bereken[A](fact: SingularFact[A]): SingularBerekenStart[A] = macro DslMacros.captureSingularBerekenSourcePositionMacroImpl[A] 51 | def Bereken[A](fact: ListFact[A]): ListBerekenStart[A] = macro DslMacros.captureListBerekenSourcePositionMacroImpl[A] 52 | } 53 | 54 | class SingularBerekenStart[T] (condition: DslCondition, output: Fact[T], berekeningenAcc: List[Derivation], sourcePosition: SourcePosition) { 55 | def is[T1 >: T](operation: DslEvaluation[T1]): BerekeningAccumulator = new BerekeningAccumulator(condition, Specificatie(condition, output, operation, sourcePosition) :: berekeningenAcc) 56 | } 57 | 58 | class ListBerekenStart[T] (condition: DslCondition, output: Fact[List[T]], berekeningenAcc: List[Derivation], sourcePosition: SourcePosition) { 59 | def is[T1 <: T](operation: DslEvaluation[List[T1]]): BerekeningAccumulator = new BerekeningAccumulator(condition, Specificatie(condition, output, operation, sourcePosition) :: berekeningenAcc) 60 | 61 | def is[B](subRunData : SubRunData[T, B]) : BerekeningAccumulator = { 62 | val c = andCombineConditions(condition, factFilledCondition(subRunData.inputList)).condition 63 | val input = subRunData.inputList :: condition.facts.toList 64 | val subRunDerivation: SubRunDerivation = SubRunDerivation(input, output, c, subRunData) 65 | new BerekeningAccumulator(condition, subRunDerivation :: berekeningenAcc) 66 | } 67 | 68 | /** 69 | * Specificeert hoe een lijst is opgebouwd uit de resultaten van een elementberekening die voor ieder element uit een invoerlijst is uitgevoerd. 70 | * 71 | * Syntax: bevat resultaten van over 72 | */ 73 | def bevat(r: ResultatenWord): LijstOpbouwConstruct[T] = new LijstOpbouwConstruct[T](condition, output, berekeningenAcc) 74 | } 75 | 76 | class ResultatenWord 77 | 78 | class LijstOpbouwConstruct[Uit](condition: DslCondition, output: Fact[List[Uit]], berekeningAcc: List[Derivation]) { 79 | def van[I](uitTeVoerenElementBerekening: ElementBerekeningReference[I, Uit]): LijstOpbouwConstructMetBerekening[I] = new LijstOpbouwConstructMetBerekening[I](uitTeVoerenElementBerekening.berekening) 80 | 81 | class LijstOpbouwConstructMetBerekening[In](elementBerekening: ElementBerekening[In, Uit]) { 82 | 83 | def over(iterator: ListFact[In]): BerekeningAccumulator = { 84 | val contextAddition: In => engine.Context = (x: In) => Map(elementBerekening.invoerFact -> x) 85 | 86 | val subRunData = new SubRunData[Uit, In](elementBerekening.berekeningen, contextAddition, iterator, elementBerekening.uitvoerFact) 87 | 88 | val topLevelCondition = andCombineConditions(condition, factFilledCondition(subRunData.inputList)).condition 89 | 90 | new BerekeningAccumulator(condition, SubRunDerivation(subRunData.inputList :: condition.facts.toList, output, topLevelCondition, subRunData) :: berekeningAcc) 91 | } 92 | } 93 | } 94 | 95 | // --- supports naming the invoer and uitvoer inside an ElementBerekening 96 | class InvoerWord{ 97 | def is[In](iteratee: Fact[In]): InvoerSpecification[In] = new InvoerSpecification[In](iteratee) 98 | } 99 | class UitvoerWord{ 100 | def is[Uit](iteratee: Fact[Uit]): UitvoerSpecification[Uit] = new UitvoerSpecification[Uit](iteratee) 101 | } 102 | 103 | class BerekeningAccumulator private[grammar](val condition: DslCondition, val derivations: List[Derivation]) { 104 | def en[A](fact: SingularFact[A]): SingularBerekenStart[A] = macro DslMacros.captureSingularBerekenSourcePositionWithAccumulatorMacroImpl[A] 105 | def en[A](fact: ListFact[A]): ListBerekenStart[A] = macro DslMacros.captureListBerekenSourcePositionWithAccumulatorMacroImpl[A] 106 | } 107 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/DslCondition.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.dsl.nl.grammar.DslCondition._ 4 | import org.scalarules.engine._ 5 | import org.scalarules.facts.Fact 6 | import org.scalarules.utils.{SourcePosition, SourceUnknown} 7 | 8 | import scala.language.implicitConversions 9 | 10 | case class DslCondition(facts: Set[Fact[Any]], condition: Condition, sourcePosition: SourcePosition = SourceUnknown()) { 11 | def en[T](rhs: Fact[T]): DslConditionPart[T] = DslConditionPart(this, rhs, andPredicate) 12 | def en(rhs: DslCondition): DslCondition = combine(this, rhs, andPredicate) 13 | 14 | def of[T](rhs: Fact[T]): DslConditionPart[T] = DslConditionPart(this, rhs, orPredicate) 15 | def of(rhs: DslCondition): DslCondition = combine(this, rhs, orPredicate) 16 | 17 | private def combine(lhs: DslCondition, rhs: DslCondition, predicate: ConditionFunction): DslCondition = 18 | DslCondition(lhs.facts ++ rhs.facts, predicate(lhs.condition, rhs.condition)) 19 | } 20 | 21 | object DslCondition { 22 | val andPredicate: ConditionFunction = (l, r) => c => l(c) && r(c) 23 | val orPredicate: ConditionFunction = (l, r) => c => l(c) || r(c) 24 | 25 | val emptyTrueCondition: DslCondition = DslCondition(Set(), _ => true) 26 | def factFilledCondition[A](fact: Fact[A]): DslCondition = DslCondition(Set(fact), Conditions.exists(fact)) 27 | 28 | def andCombineConditions(initialDslCondition: DslCondition, dslConditions: DslCondition*): DslCondition = dslConditions.foldLeft(initialDslCondition)(_ en _) 29 | def orCombineConditions(initialDslCondition: DslCondition, dslConditions: DslCondition*): DslCondition = dslConditions.foldLeft(initialDslCondition)(_ of _) 30 | } 31 | 32 | trait DslConditionImplicits { 33 | implicit def toConditionDslPart[T](factDef : Fact[T]): DslConditionPart[T] = DslConditionPart(emptyTrueCondition, factDef, andPredicate) 34 | implicit def dslEvaluationToConditionDslPart[T](dslEvaluation: DslEvaluation[T]): DslEvaluationConditionPart[T] = DslEvaluationConditionPart(emptyTrueCondition, dslEvaluation, andPredicate) 35 | val altijd: DslCondition = emptyTrueCondition 36 | } 37 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/DslConditionPart.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import DslCondition._ 4 | import org.scalarules.engine._ 5 | import org.scalarules.facts.Fact 6 | 7 | import scala.reflect.ClassTag 8 | 9 | //scalastyle:off method.name object.name 10 | 11 | sealed trait Aanwezigheid { 12 | def buildCondition[A](fact: Fact[A]): Condition 13 | } 14 | 15 | object aanwezig extends Aanwezigheid { 16 | override def buildCondition[A](fact: Fact[A]): Condition = Conditions.exists(fact) 17 | } 18 | object afwezig extends Aanwezigheid { 19 | override def buildCondition[A](fact: Fact[A]): Condition = Conditions.notExists(fact) 20 | } 21 | 22 | sealed trait DslConditionComparators[T] { 23 | 24 | def is(value: T): DslCondition = combineWith(c => lhsEvaluation(c) contains value) 25 | def is(value: Fact[T]): DslCondition = combineWith(c => lhsEvaluation(c) == value.toEval(c)) 26 | 27 | def >[A <: T : Ordering : ClassTag](value: A): DslCondition = combineWith(compareWithValue(value, implicitly[Ordering[A]].gt)) 28 | def >[A <: T : Ordering : ClassTag](value: Fact[A]): DslCondition = combineWith(compareWithEvaluation(value.toEval, implicitly[Ordering[A]].gt)) 29 | def >=[A <: T : Ordering : ClassTag](value: A): DslCondition = combineWith(compareWithValue(value, implicitly[Ordering[A]].gteq)) 30 | def >=[A <: T : Ordering : ClassTag](value: Fact[A]): DslCondition = combineWith(compareWithEvaluation(value.toEval, implicitly[Ordering[A]].gteq)) 31 | 32 | def <[A <: T : Ordering : ClassTag](value: A): DslCondition = combineWith(compareWithValue(value, implicitly[Ordering[A]].lt)) 33 | def <[A <: T : Ordering : ClassTag](value: Fact[A]): DslCondition = combineWith(compareWithEvaluation(value.toEval, implicitly[Ordering[A]].lt)) 34 | def <=[A <: T : Ordering : ClassTag](value: A): DslCondition = combineWith(compareWithValue(value, implicitly[Ordering[A]].lteq)) 35 | def <=[A <: T : Ordering : ClassTag](value: Fact[A]): DslCondition = combineWith(compareWithEvaluation(value.toEval, implicitly[Ordering[A]].lteq)) 36 | 37 | private[grammar] def lhsEvaluation: Evaluation[T] 38 | private[grammar] def combineWith(condition: Condition): DslCondition 39 | 40 | private def compareWithValue[A <: T : Ordering : ClassTag](rhsValue: A, op: (A, A) => Boolean): Condition = 41 | c => lhsEvaluation(c) match { 42 | case Some(x: A) => op(x, rhsValue) 43 | case _ => false // throw new exception? 44 | } 45 | 46 | private def compareWithEvaluation[A <: T : Ordering : ClassTag](rhsEvaluation: Evaluation[A], op: (A, A) => Boolean): Condition = 47 | c => (lhsEvaluation(c), rhsEvaluation(c)) match { 48 | case (Some(x: A), Some(y: A)) => op(x, y) 49 | case _ => false // throw new exception? 50 | } 51 | } 52 | 53 | case class DslEvaluationConditionPart[T] private[grammar](oldPart: DslCondition, dslEvaluation: DslEvaluation[T], combineMethod: ConditionFunction) extends DslConditionComparators[T] { 54 | 55 | override private[grammar] def lhsEvaluation: Evaluation[T] = dslEvaluation.evaluation 56 | override private[grammar] def combineWith(condition: Condition): DslCondition = DslCondition(oldPart.facts ++ dslEvaluation.condition.facts, combineMethod(oldPart.condition, condition)) 57 | } 58 | 59 | case class DslConditionPart[T] private[grammar](oldPart: DslCondition, fact: Fact[T], combineMethod: ConditionFunction) extends DslConditionComparators[T] { 60 | 61 | override private[grammar] def lhsEvaluation: Evaluation[T] = fact.toEval 62 | override private[grammar] def combineWith(condition: Condition): DslCondition = DslCondition(oldPart.facts + fact, combineMethod(oldPart.condition, condition)) 63 | 64 | def is(value: Aanwezigheid): DslCondition = combineWith(value.buildCondition(fact)) 65 | 66 | } 67 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/DslEvaluation.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import DslCondition._ 4 | import org.scalarules.dsl.core.temporal.LocalDate 5 | import org.scalarules.dsl.core.operators._ 6 | import org.scalarules.finance.core.Quantity 7 | import org.scalarules.finance.nl.{Bedrag, Percentage} 8 | import org.scalarules.engine._ 9 | import org.scalarules.facts.{ListFact, SingularFact} 10 | 11 | import scala.language.implicitConversions 12 | 13 | //scalastyle:off method.name 14 | 15 | class ListUnwrappingEvaluation[A](wrapped: Evaluation[List[A]]) extends Evaluation[A] { 16 | override def apply(c: Context): Option[A] = wrapped(c) match { 17 | case Some(x :: xs) => Some(x) 18 | case _ => None 19 | } 20 | } 21 | 22 | class BinaryEvaluation[-A, B, +C](lhs: Evaluation[A], rhs: Evaluation[B], operatorDefinition: BinaryOperable[A, B, C]) extends Evaluation[C] { 23 | override def apply(c: Context): Option[C] = { 24 | val lhsValues = lhs(c).getOrElse(operatorDefinition.identityLeft) 25 | val rhsValues = rhs(c).getOrElse(operatorDefinition.identityRight) 26 | 27 | Some(operatorDefinition.operation(lhsValues, rhsValues)) 28 | } 29 | 30 | override def toString: String = s"${lhs.toString} ${operatorDefinition.representation} ${rhs.toString}" 31 | } 32 | 33 | class UnaryMinusEvaluation[+A : Quantity](eval: Evaluation[A]) extends Evaluation[A] { 34 | override def apply(c: Context): Option[A] = { 35 | val ev = implicitly[Quantity[A]] 36 | 37 | Some(ev.negate(eval(c).getOrElse(ev.zero))) 38 | } 39 | } 40 | 41 | class DslEvaluation[+A](val condition: DslCondition, val evaluation: Evaluation[A]) { 42 | 43 | def +[A1 >: A, B, C](other: DslEvaluation[B])(implicit ev: Addable[A1, B, C]): DslEvaluation[C] = { 44 | newDslEvaluation(other, new BinaryEvaluation[A1, B, C](evaluation, other.evaluation, ev)) 45 | } 46 | 47 | def -[A1 >: A, B, C](other: DslEvaluation[B])(implicit ev: Subtractable[A1, B, C]): DslEvaluation[C] = { 48 | newDslEvaluation(other, new BinaryEvaluation[A1, B, C](evaluation, other.evaluation, ev)) 49 | } 50 | 51 | def *[A1 >: A, B, C](other: DslEvaluation[B])(implicit ev: Multipliable[A1, B, C]): DslEvaluation[C] = { 52 | // Values can only be multiplied with BigDecimal. But this must be commutative. In the finance DSL we solve this 53 | // with overloads, but here, we're working with generic types based on the value types. Overloading doesn't work 54 | // here, due to type erasure (Numeric[BigDecimal] erases to the same type as Numeric[Bedrag]). Therefore we need 55 | // a new type class to work around this issue. 56 | newDslEvaluation(other, new BinaryEvaluation[A1, B, C](evaluation, other.evaluation, ev)) 57 | } 58 | 59 | def /[A1 >: A, B, C](other: DslEvaluation[B])(implicit ev: Divisible[A1, B, C]): DslEvaluation[C] = { 60 | newDslEvaluation(other, new BinaryEvaluation[A1, B, C](evaluation, other.evaluation, ev)) 61 | } 62 | 63 | def unary_-[B >: A : Quantity]: DslEvaluation[B] = { 64 | DslEvaluation(condition, new UnaryMinusEvaluation[B](evaluation)) 65 | } 66 | 67 | private def newDslEvaluation[B](other: DslEvaluation[Any], newEvaluation: Evaluation[B]) = DslEvaluation(andCombineConditions(condition, other.condition), newEvaluation) 68 | private def newDslEvaluation[B](other: SingularFact[B], newEvaluation: Evaluation[B]) = DslEvaluation(andCombineConditions(condition, factFilledCondition(other)), newEvaluation) 69 | } 70 | 71 | object DslEvaluation { 72 | def apply[A](condition: DslCondition, evaluation: Evaluation[A]): DslEvaluation[A] = new DslEvaluation[A](condition, evaluation) 73 | } 74 | 75 | trait DslEvaluationImplicits { 76 | 77 | implicit def factToDslEvaluation[A](fact: SingularFact[A]): DslEvaluation[A] = DslEvaluation(factFilledCondition(fact), new SingularFactEvaluation[A](fact)) 78 | implicit def listFactToDslEvaluation[A](fact: ListFact[A]): DslEvaluation[List[A]] = DslEvaluation(factFilledCondition(fact), new ListFactEvaluation[A](fact)) 79 | 80 | implicit def intToDslEvaluation(value: Int): DslEvaluation[Int] = DslEvaluation(emptyTrueCondition, new ConstantValueEvaluation[Int](value)) 81 | implicit def intToBigDecimalDslEvaluation(value: Int): DslEvaluation[BigDecimal] = DslEvaluation(emptyTrueCondition, new ConstantValueEvaluation[BigDecimal](value)) 82 | implicit def bigDecimalToDslEvaluation(value: BigDecimal): DslEvaluation[BigDecimal] = DslEvaluation(emptyTrueCondition, new ConstantValueEvaluation[BigDecimal](value)) 83 | implicit def bedragToDslEvaluation(value: Bedrag): DslEvaluation[Bedrag] = DslEvaluation(emptyTrueCondition, new ConstantValueEvaluation[Bedrag](value)) 84 | implicit def stringToDslEvaluation(value: String): DslEvaluation[String] = DslEvaluation(emptyTrueCondition, new ConstantValueEvaluation[String](value)) 85 | implicit def percentageToDslEvaluation(value: Percentage): DslEvaluation[Percentage] = DslEvaluation(emptyTrueCondition, new ConstantValueEvaluation[Percentage](value)) 86 | implicit def dslDatumToDslEvaluation(value: LocalDate): DslEvaluation[LocalDate] = DslEvaluation(emptyTrueCondition, new ConstantValueEvaluation[Datum](value)) 87 | } 88 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/DslGenericListAggregator.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.engine._ 4 | 5 | //class DslGenericListAggregationOperation[A](listOperation: DslGenericListAggregator, condition: DslCondition, output: Fact[A], derivations: List[Derivation]) { 6 | // def van(operation: DslEvaluation[List[A]]): BerekeningAccumulator = { 7 | // val dslEvaluation: DslEvaluation[A] = new DslEvaluation[A](operation.condition, listOperation.toEvaluation(operation.evaluation)) 8 | // 9 | // new BerekeningAccumulator(condition, Specificatie(condition, output, dslEvaluation) :: derivations) 10 | // } 11 | //} 12 | 13 | trait DslGenericListAggregator { 14 | private[grammar] def toEvaluation[A](listEvaluation: Evaluation[List[A]]): Evaluation[A] 15 | 16 | def van[A](operation: DslEvaluation[List[A]]): DslEvaluation[A] = new DslEvaluation[A](operation.condition, toEvaluation(operation.evaluation)) 17 | } 18 | 19 | class SelectElementOnLiteralIndex(index: Int) extends DslGenericListAggregator { 20 | private[grammar] def toEvaluation[A](listEvaluation: Evaluation[List[A]]): Evaluation[A] = new ListIndexSelectionEvaluation[A](listEvaluation, index) 21 | } 22 | 23 | // scalastyle:off object.name 24 | object element { 25 | def apply(literal: Int): SelectElementOnLiteralIndex = new SelectElementOnLiteralIndex(literal) 26 | } 27 | // scalastyle:on object.name 28 | 29 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/DslListAggregators.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.dsl.core.operators.{Addable, Divisible, Subtractable} 4 | import org.scalarules.engine.Evaluation 5 | 6 | // scalastyle:off object.name 7 | object totaal { 8 | private[grammar] def toEvaluation[A](listEvaluation: Evaluation[List[A]], ev: Addable[A, A, A]): Evaluation[A] = 9 | new ListAggregationEvaluation[A](listEvaluation, _.reduceLeft((a, b) => ev.operation(a, b) )) 10 | 11 | def van[A](operation: DslEvaluation[List[A]])(implicit ev: Addable[A, A, A]): DslEvaluation[A] = new DslEvaluation[A](operation.condition, toEvaluation(operation.evaluation, ev)) 12 | } 13 | 14 | object substractie { 15 | private[grammar] def toEvaluation[A](listEvaluation: Evaluation[List[A]], ev: Subtractable[A, A, A]): Evaluation[A] = 16 | new ListAggregationEvaluation[A](listEvaluation, _.reduceLeft((a, b) => ev.operation(a, b) )) 17 | 18 | def van[A](operation: DslEvaluation[List[A]])(implicit ev: Subtractable[A, A, A]): DslEvaluation[A] = new DslEvaluation[A](operation.condition, toEvaluation(operation.evaluation, ev)) 19 | } 20 | 21 | object gemiddelde { 22 | private[grammar] def toEvaluation[A](listEvaluation: Evaluation[List[A]], evAddable: Addable[A, A, A], evDivisible: Divisible[A, Int, A]): Evaluation[A] = 23 | new ListAggregationEvaluation[A](listEvaluation, (lst: List[A]) => evDivisible.operation(lst.reduceLeft( evAddable.operation ), lst.size)) 24 | 25 | def van[A](operation: DslEvaluation[List[A]])(implicit evAddable: Addable[A, A, A], evDivisible: Divisible[A, Int, A]): DslEvaluation[A] = 26 | new DslEvaluation[A](operation.condition, toEvaluation(operation.evaluation, evAddable, evDivisible)) 27 | } 28 | // scalastyle:on object.name 29 | 30 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/DslListFilter.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.dsl.core.projections.ProjectedDslEvaluation 4 | import org.scalarules.engine.{Context, Evaluation} 5 | 6 | trait DslListFilterWord { 7 | val filter = new FilterWord 8 | } 9 | 10 | class FilterWord { 11 | def lijst[A](toFilter: DslEvaluation[List[A]]): DslListFilter[A] = new DslListFilter[A](toFilter) 12 | } 13 | 14 | class DslListFilter[A](toFilter: DslEvaluation[List[A]]) extends DslFilterListTrait[A, A]{ 15 | def op(value: A, values: A*): DslEvaluation[List[A]] = op(value :: values.toList) 16 | def op(values: Seq[A]): DslEvaluation[List[A]] = filter(toFilter, identity, values.contains(_)) 17 | 18 | def op(filterOperation: A => Boolean): DslEvaluation[List[A]] = filter(toFilter, identity, filterOperation) 19 | 20 | def op[B](projectedEvaluation: ProjectedDslEvaluation[A, B]): ComplexFilterWord[A, B] = new ComplexFilterWord[A, B](toFilter, projectedEvaluation.transform) 21 | } 22 | 23 | class ComplexFilterWord[A, B](toFilter: DslEvaluation[List[A]], transform: A => B) extends DslFilterListTrait[A, B]{ 24 | def van(value: B, values: B*): DslEvaluation[List[A]] = van(value :: values.toList) 25 | def van(values: Seq[B]): DslEvaluation[List[A]] = filter(toFilter, transform, values.contains(_)) 26 | 27 | def van(filterOperation: B => Boolean): DslEvaluation[List[A]] = filter(toFilter, transform, filterOperation) 28 | } 29 | 30 | sealed trait DslFilterListTrait[A, B] { 31 | protected def filter(toFilter: DslEvaluation[List[A]], transform: A => B, filterOp: B => Boolean): DslEvaluation[List[A]] = 32 | DslEvaluation(toFilter.condition, new Evaluation[List[A]] { 33 | override def apply(c: Context): Option[List[A]] = toFilter.evaluation(c).map(_.filter(value => filterOp(transform(value)))) 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/DslOrderedListAggregator.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.engine.Evaluation 4 | 5 | trait DslOrderedListAggregator { 6 | private[grammar] def toEvaluation[A : Ordering](listEvaluation: Evaluation[List[A]]): Evaluation[A] 7 | 8 | def van[A : Ordering](operation: DslEvaluation[List[A]]): DslEvaluation[A] = new DslEvaluation[A](operation.condition, toEvaluation(operation.evaluation)) 9 | } 10 | 11 | //class DslOrderedListAggregationOperation[A : Ordering](listOperation: DslOrderedListAggregator, condition: DslCondition, output: Fact[A], derivations: List[Derivation]) { 12 | // def van(operation: DslEvaluation[List[A]]): BerekeningAccumulator = { 13 | // val dslEvaluation: DslEvaluation[A] = new DslEvaluation[A](operation.condition, listOperation.toEvaluation(operation.evaluation)) 14 | // 15 | // new BerekeningAccumulator(condition, Specificatie(condition, output, dslEvaluation) :: derivations) 16 | // } 17 | //} 18 | // 19 | // scalastyle:off object.name 20 | object laagste extends DslOrderedListAggregator { 21 | private[grammar] def toEvaluation[A : Ordering](listEvaluation: Evaluation[List[A]]): Evaluation[A] = new ListAggregationEvaluation[A](listEvaluation, _.min) 22 | } 23 | 24 | object hoogste extends DslOrderedListAggregator { 25 | private[grammar] def toEvaluation[A : Ordering](listEvaluation: Evaluation[List[A]]): Evaluation[A] = new ListAggregationEvaluation[A](listEvaluation, _.max) 26 | } 27 | // scalastyle:on object.name 28 | 29 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/DslTableSelector.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import DslCondition._ 4 | import org.scalarules.engine.{Context, Evaluation} 5 | import org.scalarules.facts.Fact 6 | 7 | object DslTableSelector { 8 | var prikken = new DslTableSelector 9 | } 10 | 11 | trait Table[O, X, Y] { 12 | def get(x: X, y: Y): O 13 | } 14 | 15 | class DslTableSelector { 16 | def in[A, X, Y](inkomensLastTabel: Fact[Table[A, X, Y]]): DslTableOperation[A, X, Y] = 17 | new DslTableOperation(inkomensLastTabel/*, condition, output, derivations*/) 18 | } 19 | 20 | class DslTableOperation[A, X, Y](tableFact: Fact[Table[A, X, Y]]/*, condition: DslCondition, output: Fact[A], derivations: List[Derivation]*/) { 21 | def met(waardes: waarde[X, Y]): DslEvaluation[A] = waardes.toEvaluation(tableFact) 22 | def met(waardes: waardes[X, Y]): DslEvaluation[List[A]] = waardes.toEvaluation(tableFact) 23 | } 24 | 25 | //scalastyle:off class.name 26 | case class waarde[X, Y](xFact: Fact[X], yFact: Fact[Y]) { 27 | def toEvaluation[A](tableFact: Fact[Table[A, X, Y]]): DslEvaluation[A] = { 28 | val localCondition: DslCondition = andCombineConditions(factFilledCondition(tableFact), factFilledCondition(xFact), factFilledCondition(yFact)) 29 | val evaluation = new TableEvaluation(xFact, yFact, tableFact) 30 | 31 | DslEvaluation(localCondition, evaluation) 32 | } 33 | } 34 | 35 | case class waardes[X, Y](xFact: Fact[List[X]], yFact: Fact[Y]){ 36 | def toEvaluation[A](tableFact: Fact[Table[A, X, Y]]): DslEvaluation[List[A]] = { 37 | val localCondition: DslCondition = andCombineConditions(factFilledCondition(tableFact), factFilledCondition(xFact), factFilledCondition(yFact)) 38 | val evaluation = new RepeatedTableEvaluation(xFact, yFact, tableFact) 39 | 40 | DslEvaluation(localCondition, evaluation) 41 | } 42 | } 43 | //scalastyle:on class.name 44 | 45 | class TableEvaluation[A, X, Y](val xFact: Fact[X], val yFact: Fact[Y], val tableFact: Fact[Table[A, X, Y]]) extends Evaluation[A] { 46 | def apply(c: Context): Option[A] = tableFact.toEval(c) match { 47 | case Some(res) => Some(res.get(xFact.toEval(c).get, yFact.toEval(c).get)) 48 | case _ => None 49 | } 50 | 51 | override def toString: String = "Prikken uit een tabel" 52 | } 53 | 54 | class RepeatedTableEvaluation[A, X, Y](val xFact: Fact[List[X]], val yFact: Fact[Y], val tableFact: Fact[Table[A, X, Y]]) extends Evaluation[List[A]] { 55 | def apply(c: Context): Option[List[A]] = { 56 | val xValues = xFact.toEval(c).getOrElse(List()) 57 | val yCoordinate: Y = yFact.toEval(c).get 58 | 59 | tableFact.toEval(c) match { 60 | case Some(res) => Some(xValues.map( res.get(_, yCoordinate) )) 61 | case _ => None 62 | } 63 | } 64 | 65 | override def toString: String = "Meerdere prikken uit een tabel" 66 | } 67 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/TestMacros.sc: -------------------------------------------------------------------------------- 1 | 2 | val universe = scala.reflect.runtime.universe 3 | 4 | import org.scalarules.utils.Glossary 5 | import universe._ 6 | 7 | import scala.language.experimental.macros 8 | 9 | //val tree = q" class Test(val outer:String) { def doIt() = println(outer) } " 10 | 11 | 12 | //val rawCrap = showRaw(q" class Test(val outer:String) { def doIt() = println(outer) } ") 13 | 14 | object G extends Glossary { 15 | 16 | val Bla = defineFact[String] 17 | val Blaaat = defineListFact[String] 18 | 19 | } 20 | 21 | import org.scalarules.dsl.nl.grammar._ 22 | 23 | val bereken: Any = showRaw(q"Gegeven(altijd).Bereken(Bla)") 24 | 25 | 26 | val b2 = showRaw(q"Gegeven(altijd).Bereken(Blaaat)") 27 | 28 | 29 | /* 30 | 31 | 32 | 33 | ClassDef( 34 | Modifiers(), 35 | TypeName("Test"), 36 | List(), 37 | Template( 38 | List( 39 | Select( 40 | Ident(scala), 41 | TypeName("AnyRef") 42 | ) 43 | ), 44 | noSelfType, 45 | List( 46 | ValDef( 47 | Modifiers(PARAMACCESSOR), 48 | TermName("outer"), 49 | Ident(TypeName("String")), 50 | EmptyTree 51 | ), 52 | DefDef( 53 | Modifiers(), 54 | termNames.CONSTRUCTOR, 55 | List(), 56 | List( 57 | List( 58 | ValDef(Modifiers(PARAM | PARAMACCESSOR), 59 | TermName("outer"), 60 | Ident(TypeName("String")), 61 | EmptyTree 62 | ) 63 | ) 64 | ), 65 | TypeTree(), 66 | Block( 67 | List(pendingSuperCall), 68 | Literal(Constant(())) 69 | ) 70 | ), 71 | DefDef( 72 | Modifiers(), 73 | TermName("doIt"), 74 | List(), 75 | List( 76 | List() 77 | ), 78 | TypeTree(), 79 | Apply( 80 | Ident(TermName("println")), 81 | List( 82 | Ident(TermName("outer")) 83 | ) 84 | ) 85 | ) 86 | ) 87 | ) 88 | 89 | 90 | 91 | 92 | ) 93 | 94 | 95 | 96 | 97 | */ 98 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/berekeningen.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.derivations.Derivation 4 | import org.scalarules.facts.Fact 5 | 6 | class Berekening(berekeningAccumulators: BerekeningAccumulator*) { 7 | val berekeningen: List[Derivation] = berekeningAccumulators.flatMap(_.derivations).toList 8 | } 9 | 10 | class InvoerSpecification[In](val iteratee: Fact[In]) 11 | class UitvoerSpecification[Uit](val resultee: Fact[Uit]) 12 | 13 | /** 14 | * Een ElementBerekening is een Berekening met een enkele invoer en een enkele uitvoer parameter. Deze worden voornamelijk gebruikt bij het definieren van 15 | * Berekeningen die als elementberekening in een lijst-iteratie worden gebruikt. In de DSL kunnen deze worden vormgegeven door de volgende syntax: 16 | * 17 | * class MijnBerekening extends ElementBerekening[, ] ( 18 | */ 19 | class ElementBerekening[In, Uit](invoer: InvoerSpecification[In], uitvoer: UitvoerSpecification[Uit], berekeningAccumulators: BerekeningAccumulator*) extends Berekening(berekeningAccumulators:_*) { 20 | def invoerFact: Fact[In] = invoer.iteratee 21 | def uitvoerFact: Fact[Uit] = uitvoer.resultee 22 | } 23 | 24 | trait ElementBerekeningReference[In, Uit] { 25 | def berekening: ElementBerekening[In, Uit] 26 | } 27 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/dslEvaluations.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import DslCondition._ 4 | import org.scalarules.finance.nl.Bedrag 5 | import org.scalarules.engine._ 6 | import org.scalarules.facts.Fact 7 | 8 | //scalastyle:off object.name 9 | 10 | /* ********************************************************************************************************************************************************** * 11 | * This file contains the implicit objects which are part of the DSL. Note that they all return instances of various Evaluation classes. These extensions of * 12 | * Evaluation are defined in operationEvaluations.scala and contain the actual implementations of each operation. If you wish to add an operation to the DSL, * 13 | * please add its DSL-part to this file and its implementation to the operationEvaluations.scala file. * 14 | * ************************************************************************************************************************************************************/ 15 | 16 | object eerste { 17 | def apply[A](facts: Fact[A]*): DslEvaluation[A] = { 18 | val condition = facts.map(f => factFilledCondition(f)).reduceLeft((b, a) => orCombineConditions(b, a)) 19 | 20 | DslEvaluation[A](condition, new EersteEvaluation(facts)) 21 | } 22 | 23 | def elementVan[A](fact: Fact[List[A]]): DslEvaluation[A] = { 24 | val condition = factFilledCondition(fact) 25 | 26 | DslEvaluation[A](condition, new EersteElementEvaluation[A](fact)) 27 | } 28 | } 29 | 30 | sealed trait ReducableDslOperation { 31 | /** 32 | * @return name of the operation, used to reconstruct the expression for documentation purposes. 33 | */ 34 | protected def operationName: String 35 | 36 | private[grammar] def operation[A](func: (A, A) => A, facts: Seq[Fact[A]]): DslEvaluation[A] = { 37 | val condition = facts.map(f => factFilledCondition(f)).reduceLeft((b, a) => andCombineConditions(b, a)) 38 | 39 | DslEvaluation[A](condition, new ReducableEvaluation(operationName, func, facts)) 40 | } 41 | } 42 | 43 | object minimum extends ReducableDslOperation { 44 | override def operationName = "minimum" 45 | 46 | def apply[A: Numeric](facts: Fact[A]*): DslEvaluation[A] = { 47 | implicit val ev = implicitly[Numeric[A]] 48 | operation(ev.min, facts) 49 | } 50 | } 51 | 52 | object maximum extends ReducableDslOperation { 53 | override def operationName = "maximum" 54 | 55 | def apply[A: Numeric](facts: Fact[A]*): DslEvaluation[A] = { 56 | implicit val ev = implicitly[Numeric[A]] 57 | operation(ev.max, facts) 58 | } 59 | } 60 | 61 | object gecombineerdMaximum { 62 | def apply[A : Ordering](facts: Fact[List[A]]*): DslEvaluation[List[A]] = { 63 | val condition = facts.map(f => factFilledCondition(f)).reduceLeft((b, a) => andCombineConditions(b, a)) 64 | 65 | DslEvaluation(condition, new Evaluation[List[A]] { 66 | override def apply(c: Context): Option[List[A]] = Some(facts.map( _.toEval(c).get ).transpose.map( _.max ).toList) 67 | }) 68 | } 69 | } 70 | 71 | object gecombineerdMinimum { 72 | def apply[A : Ordering](facts: Fact[List[A]]*): DslEvaluation[List[A]] = { 73 | val condition = facts.map(f => factFilledCondition(f)).reduceLeft((b, a) => andCombineConditions(b, a)) 74 | 75 | DslEvaluation(condition, new Evaluation[List[A]] { 76 | override def apply(c: Context): Option[List[A]] = Some(facts.map( _.toEval(c).get ).transpose.map( _.min ).toList) 77 | }) 78 | } 79 | } 80 | 81 | object als { 82 | def apply[A](dslCondition: DslCondition): AlsResult[A] = new AlsResult(dslCondition) 83 | 84 | class AlsResult[A] private[grammar](alsCondition: DslCondition) { 85 | def dan(eval: DslEvaluation[A]): DanResult[A] = new DanResult(alsCondition, eval) 86 | } 87 | 88 | class DanResult[A]private[grammar](alsCondition: DslCondition, danEvaluation: DslEvaluation[A]) { 89 | def anders(andersEvaluation: DslEvaluation[A]) : DslEvaluation[A] = { 90 | val facts = alsCondition.facts ++ danEvaluation.condition.facts ++ andersEvaluation.condition.facts 91 | val condition = facts.map(f => factFilledCondition(f)).foldLeft(emptyTrueCondition)((l, r) => andCombineConditions(l, r)) 92 | val evaluation = new AlsDanElseEvaluation(alsCondition.condition, danEvaluation.evaluation, andersEvaluation.evaluation) 93 | 94 | DslEvaluation(condition, evaluation) 95 | } 96 | } 97 | } 98 | 99 | object AfgekaptOp100Euro { 100 | def apply(eval: DslEvaluation[Bedrag]): DslEvaluation[Bedrag] = { 101 | DslEvaluation(eval.condition, 102 | new Evaluation[Bedrag] { 103 | override def apply(c: Context): Option[Bedrag] = eval.evaluation(c) match { 104 | case Some(x) => Some(x.afgekaptOp100Euro) 105 | case _ => None 106 | } 107 | } 108 | ) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/meta/BerekeningReferentie.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar.meta 2 | 3 | import scala.annotation.compileTimeOnly 4 | import scala.meta._ 5 | 6 | //scalastyle:off 7 | 8 | /** 9 | * Generates a companion object for the annotated subclass of LijstBerekening and makes it implement BerekeningReference. 10 | */ 11 | @compileTimeOnly("@MacroElementBerekening not expanded") 12 | class BerekeningReferentie extends scala.annotation.StaticAnnotation { 13 | def meta[T](thunk: => T): T = thunk 14 | 15 | inline def apply(defn: Any): Any = meta { 16 | defn match { 17 | case pat @ q"..$modsA class $tname[..$tparams] ..$modsB (...$paramss) extends $template" => 18 | 19 | // Deconstruct the body to access the constructor calls, specifically the call to LijstBerekening 20 | var template"{ ..$earlyStats } with ..$ctorcalls { $param => ..$stats }" = template 21 | val lijstBerekeningTypeParametersOption = BerekeningReferentie.extractLijstBerekeningTParams(ctorcalls) 22 | 23 | if (lijstBerekeningTypeParametersOption.isDefined) { 24 | val (in, out) = lijstBerekeningTypeParametersOption.get 25 | val annotatedTypeName = tname.value 26 | 27 | val companionDefinition = s""" 28 | object $annotatedTypeName extends ElementBerekeningReference[$in, $out] { 29 | def berekening: $annotatedTypeName = new $annotatedTypeName 30 | } 31 | """.parse[Stat].get 32 | 33 | q""" 34 | $pat 35 | 36 | $companionDefinition 37 | """ 38 | } 39 | else { 40 | println(s"Warning: Failed to extract type parameters from LijstBerekening-initializer. This means there is no accompanying BerekeningReference for $tname.") 41 | pat 42 | } 43 | case x => 44 | println("Warning: @ElementBerekening annotation was not added to a class definition. It is being ignored.") 45 | x 46 | } 47 | } 48 | } 49 | 50 | object BerekeningReferentie { 51 | /** 52 | * Scans a list of constructor call ASTs to find the one associated with the ElementBerekening class and extracts its type parameters. 53 | */ 54 | def extractLijstBerekeningTParams(ctorcalls: Seq[Ctor.Call]): Option[(String, String)] = { 55 | ctorcalls.collect { 56 | case q"$expr(..$aexprssnel)" => { 57 | println(s"Matched constructor application for ${expr.syntax}") 58 | expr match { 59 | case ctor"$ctorref[..$atpesnel]" => (ctorref.syntax, atpesnel.head.syntax, atpesnel.last.syntax) 60 | case ctor"${ctorname: Ctor.Name}" => { 61 | // Crap, we couldn't match against explicitly defined type parameters, we'll have to deduce them from the first two expressions. 62 | // The required functionality is on the roadmap of Scala Meta, so perhaps with some new release in the future. 63 | 64 | (ctorname.syntax, "Any", "Any") 65 | } 66 | } 67 | } 68 | } 69 | .find( x => x._1 == "ElementBerekening" ) 70 | .map( x => (x._2, x._3) ) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/meta/DslMacros.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar.meta 2 | 3 | import org.scalarules.derivations.Derivation 4 | import org.scalarules.dsl.nl.grammar.{DslCondition, GegevenWord, ListBerekenStart, SingularBerekenStart} 5 | import org.scalarules.facts.{ListFact, SingularFact} 6 | import org.scalarules.utils.FileSourcePosition 7 | 8 | import scala.annotation.compileTimeOnly 9 | import scala.language.experimental.macros 10 | import scala.reflect.internal.util.SourceFile 11 | import scala.reflect.macros.blackbox._ 12 | 13 | object DslMacros { 14 | 15 | @compileTimeOnly("This macro stores the source position of a 'Gegeven' during compile time, no use during runtime") 16 | def captureGegevenSourcePositionMacroImpl(c: Context)(condition: c.Expr[DslCondition]): c.Expr[GegevenWord] = { 17 | val (filename, line, column, start, length) = extractSourcePosition(c) 18 | 19 | c.universe.reify { new GegevenWord(condition.splice, FileSourcePosition(filename.splice, line.splice, column.splice, start.splice, length.splice)) } 20 | } 21 | 22 | @compileTimeOnly("This macro stores the source position of a 'Bereken' during compile time, no use during runtime") 23 | def captureSingularBerekenSourcePositionMacroImpl[A : c.WeakTypeTag](c: Context)(fact: c.Expr[SingularFact[A]]): c.Expr[SingularBerekenStart[A]] = { 24 | import c.universe._ 25 | 26 | val (filename, line, column, start, length) = extractSourcePosition(c) 27 | 28 | val conditionExpr: c.Expr[DslCondition] = c.Expr[DslCondition](Select(c.prefix.tree, TermName("condition"))) 29 | 30 | c.universe.reify { new SingularBerekenStart[A](conditionExpr.splice, fact.splice, List(), FileSourcePosition(filename.splice, line.splice, column.splice, start.splice, length.splice)) } 31 | } 32 | 33 | @compileTimeOnly("This macro stores the source position of a 'Bereken' during compile time, no use during runtime") 34 | def captureSingularBerekenSourcePositionWithAccumulatorMacroImpl[A : c.WeakTypeTag](c: Context)(fact: c.Expr[SingularFact[A]]): c.Expr[SingularBerekenStart[A]] = { 35 | import c.universe._ 36 | 37 | val (filename, line, column, start, length) = extractSourcePosition(c) 38 | 39 | val conditionExpr: c.Expr[DslCondition] = c.Expr[DslCondition](Select(c.prefix.tree, TermName("condition"))) 40 | val derivationsExpr: c.Expr[List[Derivation]] = c.Expr[List[Derivation]](Select(c.prefix.tree, TermName("derivations"))) 41 | 42 | c.universe.reify { new SingularBerekenStart[A](conditionExpr.splice, fact.splice, derivationsExpr.splice, FileSourcePosition(filename.splice, line.splice, column.splice, start.splice, length.splice)) } 43 | } 44 | 45 | @compileTimeOnly("This macro stores the source position of a 'Bereken' during compile time, no use during runtime") 46 | def captureListBerekenSourcePositionMacroImpl[A : c.WeakTypeTag](c: Context)(fact: c.Expr[ListFact[A]]): c.Expr[ListBerekenStart[A]] = { 47 | import c.universe._ 48 | 49 | val (filename, line, column, start, length) = extractSourcePosition(c) 50 | 51 | val conditionExpr: c.Expr[DslCondition] = c.Expr[DslCondition](Select(c.prefix.tree, TermName("condition"))) 52 | 53 | c.universe.reify { new ListBerekenStart[A](conditionExpr.splice, fact.splice, List(), FileSourcePosition(filename.splice, line.splice, column.splice, start.splice, length.splice)) } 54 | } 55 | 56 | @compileTimeOnly("This macro stores the source position of a 'Bereken' during compile time, no use during runtime") 57 | def captureListBerekenSourcePositionWithAccumulatorMacroImpl[A : c.WeakTypeTag](c: Context)(fact: c.Expr[ListFact[A]]): c.Expr[ListBerekenStart[A]] = { 58 | import c.universe._ 59 | 60 | val (filename, line, column, start, length) = extractSourcePosition(c) 61 | 62 | val conditionExpr: c.Expr[DslCondition] = c.Expr[DslCondition](Select(c.prefix.tree, TermName("condition"))) 63 | val derivationsExpr: c.Expr[List[Derivation]] = c.Expr[List[Derivation]](Select(c.prefix.tree, TermName("derivations"))) 64 | 65 | c.universe.reify { new ListBerekenStart[A](conditionExpr.splice, fact.splice, derivationsExpr.splice, FileSourcePosition(filename.splice, line.splice, column.splice, start.splice, length.splice)) } 66 | } 67 | 68 | def extractSourcePosition(c: Context): (c.Expr[String], c.Expr[Int], c.Expr[Int], c.Expr[Int], c.Expr[Int]) = { 69 | import c.universe._ // scalastyle:ignore 70 | 71 | val Apply(methodName, _) = c.macroApplication 72 | val position: c.Position = methodName.pos 73 | val source: SourceFile = position.source 74 | 75 | val line = c.Expr(Literal(Constant(position.line))) 76 | val column = c.Expr(Literal(Constant(position.column))) 77 | val start = c.Expr(Literal(Constant(position.focus.start))) 78 | val length = c.Expr(Literal(Constant(methodName.symbol.name.toString().length))) 79 | val filename = c.Expr(Literal(Constant(source.file.name))) 80 | 81 | (filename, line, column, start, length) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/operationEvaluations.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.engine._ 4 | import org.scalarules.facts.Fact 5 | 6 | /* ********************************************************************************************************************************************************** * 7 | * This file contains the actual implementations of the Evaluations supported by the DSL. The DSL implicits will create objects of these classes to feed to * 8 | * the engine. If you need an extra operation, add its implementation here and then add support to the DSL for it in the dslEvaluations.scala file. * 9 | * ********************************************************************************************************************************************************** */ 10 | 11 | /** 12 | * Evaluation providing the first value found for an ordered sequence of Facts. When this evaluation resolves, any Facts that resolve to None will be ignored. 13 | * 14 | * @param facts a List of Facts to search values for. 15 | * @tparam A the type returned by this Evaluation. It is inferred from the type of Facts in the input Sequence. 16 | */ 17 | class EersteEvaluation[A](facts: Seq[Fact[A]]) extends Evaluation[A] { 18 | def apply(c: Context): Option[A] = facts.find(f => f.toEval(c).isDefined) match { 19 | case Some(x) => x.toEval(c) 20 | case _ => None 21 | } 22 | 23 | override def toString: String = facts.mkString("eerste(", ", ", ")") 24 | } 25 | 26 | class EersteElementEvaluation[A](fact: Fact[List[A]]) extends Evaluation[A] { 27 | def apply(c: Context): Option[A] = fact.toEval(c) match { 28 | case Some(x :: xs) => Some(x) 29 | case _ => None 30 | } 31 | 32 | override def toString: String = s"eerste element van(${fact.name})" 33 | } 34 | 35 | /** 36 | * Evaluation providing a framework for operations which can reduce a Sequence of Facts. When this evaluation resolves, the operation parameter will be applied 37 | * to the Sequence of Facts using the reduceLeft function. 38 | * 39 | * @param operationName name of the operation. This is used in the toString function for documentation purposes. (You should preferably supply the same word as 40 | * is used in the DSL to create the operation. For examples, see maximum and minimum in dslEvaluations.) 41 | * @param operation the actual function capable of reducing a Sequence of Facts. 42 | * @param facts the Sequence of input Facts. 43 | * @tparam A type of the input Facts. 44 | * @tparam B type of the result of the reducing operation. Must be a supertype of type A to fit into the reduceLeft operation. 45 | */ 46 | class ReducableEvaluation[A, B >: A](val operationName: String, val operation: (B, A) => B, val facts: Seq[Fact[A]]) extends Evaluation[B] { 47 | override def apply(c: Context): Option[B] = facts.flatMap(f => f.toEval(c)).toList match { 48 | case Nil => None 49 | case list@(x :: xs) => Some(list.reduceLeft((l: B, r: A) => operation(l, r))) 50 | } 51 | 52 | override def toString: String = facts.mkString(s"$operationName(", ", ", ")") 53 | } 54 | 55 | /** 56 | * Evaluation providing an if-then-else expression. When this evaluation resolves, the condition is evaluated first. If its result is true, the danFact will be 57 | * used, otherwise the andersFact will be used. 58 | * 59 | * @param condition the Condition on which to base the choice of values. 60 | * @param danEvaluation the Evaluation to resolve the value of when the condition is true. 61 | * @param andersEvaluation the Evaluation to resolve the value of when the condition is false. 62 | * @tparam A type of the result and consequently the input Facts. 63 | */ 64 | class AlsDanElseEvaluation[A](val condition: Condition, val danEvaluation: Evaluation[A], val andersEvaluation: Evaluation[A]) extends Evaluation[A] { 65 | override def apply(c: Context): Option[A] = if (condition(c)) danEvaluation(c) else andersEvaluation(c) 66 | } 67 | 68 | class ListIndexSelectionEvaluation[+A](eval: Evaluation[List[A]], index: Int) extends Evaluation[A] { 69 | override def apply(c: Context): Option[A] = { 70 | eval(c) match { 71 | case Some(lst@(x :: xs)) if index < lst.size => Some(lst(index)) 72 | case _ => None 73 | } 74 | } 75 | } 76 | 77 | class ListAggregationEvaluation[+A](eval: Evaluation[List[A]], aggregator: List[A] => A) extends Evaluation[A] { 78 | override def apply(c: Context): Option[A] = { 79 | eval(c) match { 80 | case Some(lst@x :: xs) => Some(aggregator(lst)) 81 | case _ => None 82 | } 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/dsl/nl/grammar/package.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl 2 | 3 | /** 4 | * Aggregates the keywords and implicit definitions of the Scala-Rules DSL. Import this package's members to 5 | * use the DSL in your files. 6 | */ 7 | package object grammar extends ScalaRulesDsl { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/service/dsl/BusinessService.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.service.dsl 2 | 3 | import org.scalarules.derivations.Derivation 4 | import org.scalarules.dsl.nl.grammar.Berekening 5 | import org.scalarules.facts.Fact 6 | import org.scalarules.utils.Glossary 7 | import org.scalarules.engine._ 8 | 9 | import scala.util.{Failure, Success, Try} 10 | 11 | /** 12 | * Een BusinessService stelt de gebruiker in staat om te specificeren (en te ontsluiten): 13 | * - dat bepaalde berekeningen bij elkaar gegroepeerd zijn (dit wordt afgedwongen), 14 | * - dat sommige invoerfeiten verplicht zijn om te vullen (dit wordt afgedwongen), 15 | * - welke defaults er gelden voor niet-verplichte invoer, 16 | * - welke feiten specifiek dienen als uitvoer: de "resultaten" van de berekening, 17 | * - welke feiten Uberhaupt beschikbaar worden gesteld aan de buitenwereld (glossaries). 18 | * 19 | * Iedere BusinessService heeft een run-methode die afdwingt dat er eerst gevalideerd wordt of alle verplichte invoer aanwezig is. 20 | */ 21 | case class BusinessService(berekeningen: List[Berekening], 22 | glossaries: List[Glossary], 23 | verplichteInvoerFacts: List[Fact[Any]], 24 | optioneleInvoerFacts: Map[Fact[Any], Any], 25 | uitvoerFacts: List[Fact[Any]]) { 26 | 27 | type ErrorMessage = String 28 | 29 | val optioneleInvoerFactsList: List[Fact[Any]] = optioneleInvoerFacts.keys.toList 30 | 31 | def validate(inputContext: Context): List[ErrorMessage] = 32 | validateSpecifications ::: validateInvoer(inputContext) 33 | 34 | def validateSpecifications: List[ErrorMessage] = 35 | validateBerekeningen ::: validateGlossaries ::: validateAllFactsInGlossary ::: validateNoFactsBothOptionalAndMandatoryInvoer ::: validateUitvoer 36 | 37 | def validateBerekeningen: List[ErrorMessage] = berekeningen match { 38 | case Nil => List("no Berekeningen specified!") 39 | case list: List[Berekening] => validateNoRedundancies(list) 40 | } 41 | 42 | private def validateNoRedundancies(list: List[Any]) = 43 | list.map(_.getClass) 44 | .groupBy(identity) 45 | .collect{ 46 | case (berekening, occurrences) if occurrences.length > 1 => berekening.toString + " was specified more than once!" 47 | }.toList 48 | 49 | def validateGlossaries() : List[ErrorMessage] = glossaries match { 50 | case Nil => List("no Glossaries specified!") 51 | case list: List[Glossary] => validateNoRedundancies(list) 52 | } 53 | 54 | def validateAllFactsInGlossary() : List[ErrorMessage] = { 55 | val factsInScope: List[Fact[Any]] = glossaries.foldLeft(List():List[Fact[Any]])((a: List[Fact[Any]], b: Glossary) => a ++ b.facts.values) 56 | val definedBusinessServiceFacts: List[Fact[Any]] = optioneleInvoerFactsList ::: verplichteInvoerFacts ::: uitvoerFacts 57 | 58 | definedBusinessServiceFacts.filterNot(factsInScope.contains(_)).map( 59 | notFound => s"$notFound specified, but not found in specified glossaries so not in scope!") 60 | } 61 | 62 | def validateNoFactsBothOptionalAndMandatoryInvoer(): List[ErrorMessage] = 63 | verplichteInvoerFacts.intersect(optioneleInvoerFactsList).map( 64 | doubleFact => s"$doubleFact specified as both verplichteInvoer and optioneleInvoer") 65 | 66 | def validateInvoer(inputContext: Context): List[ErrorMessage] = verplichteInvoerFacts match { 67 | case Nil => List("no verplichteInvoer specified, but this is mandatory!") 68 | case list: List[Fact[Any]] => list.collect { case fact: Fact[Any] if !inputContext.contains(fact) => s"$fact not provided in context, but mandatory!"} 69 | } 70 | 71 | def validateUitvoer(): List[ErrorMessage] = uitvoerFacts match { 72 | case Nil => List("no uitvoerFacts specified, but this is mandatory!") 73 | case _ => Nil 74 | } 75 | 76 | def run(inputContext: Context, runFunction: (Context, List[Derivation]) => Context): Try[Context] = 77 | validate(inputContext) match { 78 | case Nil => Success(runFunction(optioneleInvoerFacts ++ inputContext, berekeningen.flatMap(_.berekeningen))) 79 | case list: List[ErrorMessage] => Failure( 80 | new IllegalStateException(list.reduceLeft((a: ErrorMessage, b: ErrorMessage) => a + " / " + b)) 81 | ) 82 | } 83 | 84 | def runDebug(inputContext: Context, runFunction: (Context, List[Derivation]) => (Context, List[Step])): Try[(Context, List[Step])] = 85 | validate(inputContext) match { 86 | case Nil => Success(runFunction(optioneleInvoerFacts ++ inputContext, berekeningen.flatMap(_.berekeningen))) 87 | case list: List[ErrorMessage] => Failure( 88 | new IllegalStateException(list.reduceLeft((a: ErrorMessage, b: ErrorMessage) => a + " / " + b)) 89 | ) 90 | } 91 | } 92 | 93 | object BusinessServiceHelper { 94 | val Geen = Map.empty[Fact[Any], String] 95 | } 96 | -------------------------------------------------------------------------------- /engine/src/main/scala/org/scalarules/utils/GlossaryConverter.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.utils 2 | 3 | import org.scalarules.facts.Fact 4 | 5 | import scala.reflect.ClassTag 6 | 7 | object GlossaryConverter { 8 | 9 | def toJson(g: Glossary): String = { 10 | g.facts.map( factToJson ).mkString("{", ", ", "}") 11 | } 12 | 13 | private def factToJson[A : ClassTag](fe: (String, Fact[A])): String = { 14 | val (name, fact) = fe 15 | val classTag = implicitly[ClassTag[A]] 16 | s""" "${name}": { 17 | | "name": "${name}", 18 | | "description": "${fact.description}", 19 | |} 20 | """.stripMargin 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/core/DivisibleValuesBerekening.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core 2 | 3 | import org.scalarules.dsl.nl.grammar._ 4 | import DivisibleValuesGlossary._ 5 | 6 | class DivisibleValuesBerekening extends Berekening ( 7 | 8 | Gegeven (altijd) 9 | Bereken PercentageA is BedragA / BedragB 10 | , 11 | 12 | Gegeven (altijd) 13 | Bereken BedragC is BedragD / PercentageB 14 | 15 | ) 16 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/core/DivisibleValuesGlossary.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core 2 | 3 | import org.scalarules.facts.SingularFact 4 | import org.scalarules.finance.nl.{Bedrag, Percentage} 5 | import org.scalarules.utils.Glossary 6 | 7 | object DivisibleValuesGlossary extends Glossary { 8 | val BedragA = defineFact[Bedrag] 9 | val BedragB = defineFact[Bedrag] 10 | val BedragC = defineFact[Bedrag] 11 | val BedragD = defineFact[Bedrag] 12 | 13 | val PercentageA = defineFact[Percentage] 14 | val PercentageB = defineFact[Percentage] 15 | 16 | } 17 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/core/DivisibleValuesTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core 2 | 3 | import org.scalarules.finance.nl._ 4 | import DivisibleValuesGlossary._ 5 | import org.scalarules.utils.InternalBerekeningenTester 6 | 7 | class DivisibleValuesTest extends InternalBerekeningenTester(new DivisibleValuesBerekening) { 8 | 9 | test("Percentage tussen twee bedragen") gegeven ( 10 | BedragA is 400.euro, 11 | BedragB is 800.euro 12 | ) verwacht ( 13 | PercentageA is 50.procent 14 | ) 15 | 16 | test("Bedrag gedeeld door percentage geeft bedrag") gegeven ( 17 | BedragD is 400.euro, 18 | PercentageB is 50.procent 19 | ) verwacht ( 20 | BedragC is 800.euro 21 | ) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/core/LijstBewerkingen.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core 2 | 3 | import org.scalarules.finance.nl._ 4 | import org.scalarules.dsl.nl.grammar._ 5 | import LijstBewerkingenGlossary$._ 6 | import org.scalarules.dsl.nl.grammar.DslCondition._ 7 | import org.scalarules.engine._ 8 | 9 | import scala.language.implicitConversions 10 | 11 | class NietLijstBewerkingen extends Berekening ( 12 | Gegeven (altijd) 13 | Bereken 14 | GetalC is GetalA * GetalB en 15 | GetalD is GetalA + GetalB en 16 | GetalE is GetalA - GetalB en 17 | GetalF is GetalA / GetalB 18 | ) 19 | 20 | class LijstOptellingen extends Berekening ( 21 | Gegeven (altijd) 22 | Bereken 23 | OptellingLijstEnLijst is InvoerLijstA + InvoerLijstB en 24 | OptellingLijstEnGetal is InvoerLijstA + GetalA en 25 | OptellingGetalEnLijst is GetalA + InvoerLijstA en 26 | OptellingEnkeleLijstEnEnkeleLijst is InvoerLijstEnkeleC + InvoerLijstEnkeleD en 27 | OptellingEnkeleLijstEnGetal is InvoerLijstEnkeleC + GetalA en 28 | OptellingGetalEnEnkeleLijst is GetalA + InvoerLijstEnkeleD 29 | ) 30 | 31 | class LijstSubtracties extends Berekening ( 32 | Gegeven (altijd) 33 | Bereken 34 | SubtractieLijstEnLijst is InvoerLijstA - InvoerLijstB en 35 | SubtractieLijstEnGetal is InvoerLijstA - GetalA en 36 | SubtractieGetalEnLijst is GetalA - InvoerLijstA en 37 | SubtractieEnkeleLijstEnEnkeleLijst is InvoerLijstEnkeleC - InvoerLijstEnkeleD en 38 | SubtractieEnkeleLijstEnGetal is InvoerLijstEnkeleC - GetalA en 39 | SubtractieGetalEnEnkeleLijst is GetalA - InvoerLijstEnkeleD en 40 | SubtractieLijstVanLijstResultaat is (substractie van SubtractieLijstVanLijst) 41 | ) 42 | 43 | class LijstVermenigvuldigingen extends Berekening ( 44 | Gegeven (altijd) 45 | Bereken 46 | VermenigvuldigingLijstEnLijst is InvoerLijstA * InvoerLijstB en 47 | VermenigvuldigingLijstEnGetal is InvoerLijstA * GetalA en 48 | VermenigvuldigingGetalEnLijst is GetalA * InvoerLijstA en 49 | VermenigvuldigingEnkeleLijstEnEnkeleLijst is InvoerLijstEnkeleC * InvoerLijstEnkeleD en 50 | VermenigvuldigingEnkeleLijstEnGetal is InvoerLijstEnkeleC * GetalA en 51 | VermenigvuldigingGetalEnEnkeleLijst is GetalA * InvoerLijstEnkeleD 52 | ) 53 | 54 | class LijstDelingen extends Berekening ( 55 | Gegeven (altijd) 56 | Bereken 57 | DelingLijstEnLijst is InvoerLijstA / InvoerLijstB en 58 | DelingLijstEnGetal is InvoerLijstA / GetalA en 59 | DelingGetalEnLijst is GetalA / InvoerLijstA en 60 | DelingEnkeleLijstEnEnkeleLijst is InvoerLijstEnkeleC / InvoerLijstEnkeleD en 61 | DelingEnkeleLijstEnGetal is InvoerLijstEnkeleC / GetalA en 62 | DelingGetalEnEnkeleLijst is GetalA / InvoerLijstEnkeleD 63 | ) 64 | 65 | class LijstElementKeuzes extends Berekening ( 66 | Gegeven (altijd) 67 | Bereken 68 | EersteElementVan is (element(0) van InvoerLijstA) en 69 | LaagsteElementA is (laagste van InvoerLijstB) en 70 | LaagsteElementB is (laagste van InvoerLijstSerieE) en 71 | LaagsteElementC is (laagste van InvoerLijstF) en 72 | HoogsteElementA is (hoogste van InvoerLijstB) en 73 | HoogsteElementB is (hoogste van InvoerLijstSerieE) en 74 | HoogsteElementC is (hoogste van InvoerLijstF) 75 | ) 76 | 77 | class LijstGemiddelden extends Berekening ( 78 | Gegeven (altijd) 79 | Bereken 80 | GemiddeldeA is (gemiddelde van InvoerLijstB) en 81 | GemiddeldeB is (gemiddelde van InvoerLijstSerieE) en 82 | GemiddeldeC is (gemiddelde van InvoerLijstF) en 83 | GemiddeldeD is (gemiddelde van InvoerLijstEnkeleC) en 84 | GemiddeldeE is (gemiddelde van InvoerLijstLeegG) en 85 | GemiddeldeListA is (gemiddelde van InvoerLijstVanLijstA) 86 | ) 87 | 88 | class LijstSommaties extends Berekening ( 89 | Gegeven (altijd) 90 | Bereken 91 | SommatieA is (totaal van InvoerLijstA) en 92 | SommatieB is (totaal van InvoerLijstEnkeleC) en 93 | SommatieC is (totaal van InvoerLijstLeegG) en 94 | SommatieListA is (totaal van InvoerLijstVanLijstA) 95 | ) 96 | 97 | class LijstConditionals extends Berekening ( 98 | Gegeven (altijd) 99 | Bereken 100 | AlsDanPerElementA is DummyFunction(InvoerLijstBedragen, 33.procent, 100.procent) 101 | ) 102 | 103 | class LijstInLijstOptelling extends Berekening ( 104 | Gegeven (altijd) 105 | Bereken 106 | LijstInLijstC is LijstInLijstA + LijstInLijstB en 107 | LijstInLijstOptellingA is (totaal van LijstInLijstA) en 108 | LijstInLijstBO is (totaal van LijstInLijstInLijstA) 109 | ) 110 | 111 | object DummyFunction { 112 | def apply[T](input: DslEvaluation[List[Bedrag]], wegingPositiefInkomen: DslEvaluation[Percentage], wegingNegatiefInkomen: DslEvaluation[Percentage]): DslEvaluation[List[Percentage]] = { 113 | val newCondition = andCombineConditions(input.condition, wegingPositiefInkomen.condition, wegingNegatiefInkomen.condition) 114 | 115 | DslEvaluation(newCondition, new Evaluation[List[Percentage]] { 116 | override def apply(c: Context): Option[List[Percentage]] = { 117 | val wegingsFactorP: Percentage = wegingPositiefInkomen.evaluation(c).get 118 | val wegingsFactorN: Percentage = wegingNegatiefInkomen.evaluation(c).get 119 | 120 | input.evaluation(c) match { 121 | case Some(x) => Some(x.map ( a => if (a < 0.euro) wegingsFactorN else wegingsFactorP )) 122 | case _ => None 123 | } 124 | } 125 | }) 126 | } 127 | } -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/core/LijstBewerkingenGlossary$.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core 2 | 3 | import org.scalarules.finance.nl.{Bedrag, Percentage} 4 | import org.scalarules.utils.Glossary 5 | 6 | object LijstBewerkingenGlossary$ extends Glossary { 7 | 8 | val GetalA = defineFact[BigDecimal] 9 | val GetalB = defineFact[BigDecimal] 10 | val GetalC = defineFact[BigDecimal] 11 | val GetalD = defineFact[BigDecimal] 12 | val GetalE = defineFact[BigDecimal] 13 | val GetalF = defineFact[BigDecimal] 14 | 15 | val BedragA = defineFact[Bedrag] 16 | val BedragB = defineFact[Bedrag] 17 | 18 | val InvoerLijstA = defineListFact[BigDecimal] 19 | val InvoerLijstB = defineListFact[BigDecimal] 20 | val InvoerLijstEnkeleC = defineListFact[BigDecimal] 21 | val InvoerLijstEnkeleD = defineListFact[BigDecimal] 22 | val InvoerLijstSerieE = defineListFact[BigDecimal] 23 | val InvoerLijstF = defineListFact[BigDecimal] 24 | val InvoerLijstLeegG = defineListFact[BigDecimal] 25 | val InvoerLijstVanLijstA = defineListFact[List[BigDecimal]] 26 | 27 | val InvoerLijstBedragen = defineListFact[Bedrag] 28 | 29 | val OptellingLijstEnLijst = defineListFact[BigDecimal] 30 | val OptellingLijstEnGetal = defineListFact[BigDecimal] 31 | val OptellingGetalEnLijst = defineListFact[BigDecimal] 32 | val OptellingEnkeleLijstEnEnkeleLijst = defineListFact[BigDecimal] 33 | val OptellingEnkeleLijstEnGetal = defineListFact[BigDecimal] 34 | val OptellingGetalEnEnkeleLijst = defineListFact[BigDecimal] 35 | 36 | val SubtractieLijstVanLijstResultaat = defineListFact[BigDecimal] 37 | val SubtractieLijstVanLijst = defineListFact[List[BigDecimal]] 38 | 39 | val SubtractieLijstEnLijst = defineListFact[BigDecimal] 40 | val SubtractieLijstEnGetal = defineListFact[BigDecimal] 41 | val SubtractieGetalEnLijst = defineListFact[BigDecimal] 42 | val SubtractieEnkeleLijstEnEnkeleLijst = defineListFact[BigDecimal] 43 | val SubtractieEnkeleLijstEnGetal = defineListFact[BigDecimal] 44 | val SubtractieGetalEnEnkeleLijst = defineListFact[BigDecimal] 45 | 46 | val VermenigvuldigingLijstEnLijst = defineListFact[BigDecimal] 47 | val VermenigvuldigingLijstEnGetal = defineListFact[BigDecimal] 48 | val VermenigvuldigingGetalEnLijst = defineListFact[BigDecimal] 49 | val VermenigvuldigingEnkeleLijstEnEnkeleLijst = defineListFact[BigDecimal] 50 | val VermenigvuldigingEnkeleLijstEnGetal = defineListFact[BigDecimal] 51 | val VermenigvuldigingGetalEnEnkeleLijst = defineListFact[BigDecimal] 52 | 53 | val DelingLijstEnLijst = defineListFact[BigDecimal] 54 | val DelingLijstEnGetal = defineListFact[BigDecimal] 55 | val DelingGetalEnLijst = defineListFact[BigDecimal] 56 | val DelingEnkeleLijstEnEnkeleLijst = defineListFact[BigDecimal] 57 | val DelingEnkeleLijstEnGetal = defineListFact[BigDecimal] 58 | val DelingGetalEnEnkeleLijst = defineListFact[BigDecimal] 59 | 60 | val EersteElementVan = defineFact[BigDecimal] 61 | 62 | val LaagsteElementA = defineFact[BigDecimal] 63 | val LaagsteElementB = defineFact[BigDecimal] 64 | val LaagsteElementC = defineFact[BigDecimal] 65 | 66 | val HoogsteElementA = defineFact[BigDecimal] 67 | val HoogsteElementB = defineFact[BigDecimal] 68 | val HoogsteElementC = defineFact[BigDecimal] 69 | 70 | val GemiddeldeA = defineFact[BigDecimal] 71 | val GemiddeldeB = defineFact[BigDecimal] 72 | val GemiddeldeC = defineFact[BigDecimal] 73 | val GemiddeldeD = defineFact[BigDecimal] 74 | val GemiddeldeE = defineFact[BigDecimal] 75 | val GemiddeldeListA = defineListFact[BigDecimal] 76 | 77 | val SommatieA = defineFact[BigDecimal] 78 | val SommatieB = defineFact[BigDecimal] 79 | val SommatieC = defineFact[BigDecimal] 80 | val SommatieListA = defineListFact[BigDecimal] 81 | 82 | val AlsDanPerElementA = defineListFact[Percentage] 83 | 84 | val LijstInLijstA = defineListFact[List[BigDecimal]] 85 | val LijstInLijstB = defineListFact[List[BigDecimal]] 86 | val LijstInLijstC = defineListFact[List[BigDecimal]] 87 | 88 | val LijstInLijstOptellingA = defineListFact[BigDecimal] 89 | 90 | val LijstInLijstInLijstA = defineListFact[List[List[BigDecimal]]] 91 | val LijstInLijstBO = defineListFact[List[BigDecimal]] 92 | } 93 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/core/TableSelectorBerekening.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core 2 | 3 | import org.scalarules.finance.nl._ 4 | import org.scalarules.dsl.nl.grammar._ 5 | import TableSelectorGlossary._ 6 | import org.scalarules.dsl.nl.grammar.DslCondition._ 7 | import org.scalarules.dsl.nl.grammar.DslTableSelector.prikken 8 | import org.scalarules.engine._ 9 | 10 | class TableSelectorBerekening extends { 11 | } with Berekening ( 12 | Gegeven (altijd) 13 | Bereken 14 | ResultString is (prikken in TableFact met waarde(IndexX, IndexY)) en 15 | ResultList is (prikken in TableFact met waardes(IndexXRange, IndexY)) 16 | 17 | ) -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/core/TableSelectorGlossary.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core 2 | 3 | import org.scalarules.dsl.nl.grammar.Table 4 | import org.scalarules.utils.Glossary 5 | 6 | object TableSelectorGlossary extends Glossary { 7 | 8 | val IndexX = defineFact[Int] 9 | val IndexY = defineFact[Int] 10 | val ResultString = defineFact[String] 11 | val TableFact = defineFact[Table[String, Int, Int]] 12 | 13 | val IndexXRange = defineListFact[Int] 14 | val ResultList = defineListFact[String] 15 | } 16 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/core/TableSelectorTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core 2 | 3 | import org.scalarules.dsl.core.TableSelectorGlossary._ 4 | import org.scalarules.finance.nl._ 5 | import org.scalarules.dsl.nl.grammar.{Table, aanwezig} 6 | import org.scalarules.utils.{InternalBerekeningenTester, lijst} 7 | 8 | class TableSelectorTest extends InternalBerekeningenTester(new TableSelectorBerekening) { 9 | 10 | val simpleTable = new Table[String, Int, Int] { 11 | override def get(x: Int, y: Int): String = "Hello World" 12 | } 13 | 14 | test("eenvoudige Table Test") gegeven ( 15 | IndexX is 1, 16 | IndexY is 1, 17 | TableFact is simpleTable 18 | ) verwacht ( 19 | ResultString is "Hello World" 20 | ) 21 | 22 | } 23 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/core/projections/ProjectableFieldsCalculation.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.projections 2 | 3 | import scala.language.implicitConversions 4 | import org.scalarules.dsl.core.projections.ProjectableFieldsGlossary._ 5 | import org.scalarules.dsl.core.projections.ComplexObjectProjections.toProjections 6 | import org.scalarules.dsl.nl.grammar._ 7 | import org.scalarules.facts.{Fact, ListFact, SingularFact} 8 | 9 | class ProjectableFieldsCalculation extends Berekening( 10 | Gegeven(altijd) Bereken 11 | IntFact is ComplexFact.intValue + IntFact2 en 12 | StringFactList is ComplexFactList.stringValue 13 | ) 14 | 15 | case class ComplexObject(intValue: Int, stringValue: String) 16 | 17 | object ComplexObjectProjections { 18 | implicit def toProjections(fact: SingularFact[ComplexObject]): ComplexObjectProjections = new ComplexObjectProjections(fact) 19 | implicit def toProjections(fact: ListFact[ComplexObject]): ComplexObjectListProjections = new ComplexObjectListProjections(fact) 20 | } 21 | 22 | class ComplexObjectProjections(complexFact: Fact[ComplexObject]) extends ProjectableFields[ComplexObject] { 23 | override protected def outerFact: Fact[ComplexObject] = complexFact 24 | 25 | val intValue: DslEvaluation[Int] = projectField(_.intValue) 26 | val stringValue: DslEvaluation[String] = projectField(_.stringValue) 27 | } 28 | 29 | class ComplexObjectListProjections(complexFact: ListFact[ComplexObject]) extends ProjectableListFields[ComplexObject] { 30 | override protected def outerFact: ListFact[ComplexObject] = complexFact 31 | 32 | val intValue: DslEvaluation[List[Int]] = projectField(_.intValue) 33 | val stringValue: DslEvaluation[List[String]] = projectField(_.stringValue) 34 | } 35 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/core/projections/ProjectableFieldsGlossary.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.projections 2 | 3 | import org.scalarules.utils.Glossary 4 | 5 | object ProjectableFieldsGlossary extends Glossary { 6 | val ComplexFact = defineFact[ComplexObject] 7 | val IntFact = defineFact[Int] 8 | val IntFact2 = defineFact[Int] 9 | 10 | val ComplexFactList = defineListFact[ComplexObject] 11 | val StringFactList = defineListFact[String] 12 | } 13 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/core/projections/ProjectableFieldsTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.core.projections 2 | 3 | import org.scalarules.utils.InternalBerekeningenTester 4 | import org.scalarules.dsl.core.projections.ProjectableFieldsGlossary._ 5 | 6 | class ProjectableFieldsTest extends InternalBerekeningenTester(new ProjectableFieldsCalculation) { 7 | 8 | test("show Projections work by extracting intValue from a Complex Object") gegeven ( 9 | ComplexFact is ComplexObject(2, "Hello "), 10 | IntFact2 is 5 11 | ) verwacht ( 12 | IntFact is 7 13 | ) 14 | 15 | test("show ListProjections work by extracting values from a Complex Object List") gegeven ( 16 | ComplexFactList is List(ComplexObject(2, "Hello "), ComplexObject(1, "World")) 17 | ) verwacht ( 18 | StringFactList is List("Hello ", "World") 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/datum/DatumImplicitsTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.datum 2 | 3 | import org.scalarules.finance.nl.{Bedrag, Percentage} 4 | import org.scalarules.engine.{Context, FactEngine} 5 | import org.scalatest.{FlatSpec, Matchers} 6 | import org.scalarules.dsl.nl.grammar._ 7 | import org.scalarules.facts.SingularFact 8 | 9 | class DatumImplicitsTest extends FlatSpec with Matchers { 10 | 11 | val sutDatumA = new SingularFact[Datum]("sutDatumA") 12 | val sutDatumB = new SingularFact[Datum]("sutDatumB") 13 | val result = new SingularFact[Boolean]("result") 14 | val expected = new SingularFact[Boolean]("expected") 15 | 16 | it should "compile" in { 17 | Gegeven(sutDatumA < sutDatumB) 18 | Gegeven(sutDatumA <= sutDatumB) 19 | Gegeven(sutDatumA > sutDatumB) 20 | Gegeven(sutDatumA >= sutDatumB) 21 | Gegeven(sutDatumA is sutDatumB) 22 | } 23 | 24 | it should "compare dates" in { 25 | check( Gegeven(sutDatumA < sutDatumB), true, false, false ) 26 | } 27 | 28 | def check(gegeven: GegevenWord, expectSmaller: Boolean, expectEqual: Boolean, expectGreater: Boolean): Unit = { 29 | val accumulator: BerekeningAccumulator = gegeven Bereken( result ) is( expected ) 30 | 31 | val resultContext: Context = FactEngine.runNormalDerivations(Map(sutDatumA -> "01-01-2015".datum, sutDatumB -> "02-01-2015".datum), accumulator.derivations) 32 | val evaluatedResult: Boolean = result.toEval(resultContext).getOrElse(false) 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/datum/DatumTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.datum 2 | 3 | import org.joda.time.LocalDate 4 | import org.scalarules.dsl.nl.grammar.aanwezig 5 | import org.scalarules.utils.InternalBerekeningenTester 6 | import org.scalarules.dsl.nl.datum.DatumTestGlossary._ 7 | 8 | class DatumTest extends InternalBerekeningenTester(new DatumTestsBerekening) with DatumImplicits { 9 | 10 | val supportedDateFormats = Set( 11 | "d-M-yyyy", 12 | "d-MM-yyyy", 13 | "dd-M-yyyy", 14 | "dd-MM-yyyy" 15 | ) 16 | 17 | val datumEerder = new LocalDate(2014, 1, 1) 18 | val datumGelijk = new LocalDate(2015, 1, 1) 19 | val datumLater = new LocalDate(2016, 1, 1) 20 | 21 | supportedDateFormats.foreach( pattern => { 22 | 23 | test(s"${pattern} parsen werkt (1/3)") gegeven ( 24 | InvoerDatum is datumEerder.toString(pattern).datum 25 | ) verwacht ( 26 | EerderDan is "success", 27 | EerderDanGelijk is "success", 28 | LaterDan niet aanwezig, 29 | LaterDanGelijk niet aanwezig, 30 | GelijkAan niet aanwezig 31 | ) 32 | 33 | test(s"${pattern} parsen werkt (2/3)") gegeven ( 34 | InvoerDatum is datumLater.toString(pattern).datum 35 | ) verwacht ( 36 | EerderDan niet aanwezig, 37 | EerderDan niet aanwezig, 38 | LaterDan is "success", 39 | LaterDanGelijk is "success", 40 | GelijkAan niet aanwezig 41 | ) 42 | 43 | test(s"${pattern} parsen werkt (3/3)") gegeven ( 44 | InvoerDatum is datumGelijk.toString(pattern).datum 45 | ) verwacht ( 46 | EerderDan niet aanwezig, 47 | EerderDanGelijk is "success", 48 | LaterDan niet aanwezig, 49 | LaterDanGelijk is "success", 50 | GelijkAan is "success" 51 | ) 52 | 53 | }) 54 | 55 | } 56 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/datum/DatumTestGlossary.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.datum 2 | 3 | import org.scalarules.utils.Glossary 4 | 5 | object DatumTestGlossary extends Glossary { 6 | 7 | val InvoerDatum = defineFact[Datum] 8 | 9 | val EerderDan = defineFact[String] 10 | val EerderDanGelijk = defineFact[String] 11 | val LaterDan = defineFact[String] 12 | val LaterDanGelijk = defineFact[String] 13 | val GelijkAan = defineFact[String] 14 | 15 | } 16 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/datum/DatumTestsBerekening.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.datum 2 | 3 | import org.scalarules.dsl.nl.grammar._ 4 | import org.scalarules.dsl.nl.datum.DatumTestGlossary._ 5 | 6 | class DatumTestsBerekening extends Berekening ( 7 | 8 | Gegeven (InvoerDatum < "01-01-2015".datum) 9 | Bereken EerderDan is "success" 10 | , 11 | Gegeven (InvoerDatum <= "01-01-2015".datum) 12 | Bereken EerderDanGelijk is "success" 13 | , 14 | Gegeven (InvoerDatum > "01-01-2015".datum) 15 | Bereken LaterDan is "success" 16 | , 17 | Gegeven (InvoerDatum >= "01-01-2015".datum) 18 | Bereken LaterDanGelijk is "success" 19 | , 20 | Gegeven (InvoerDatum is "01-01-2015".datum) 21 | Bereken GelijkAan is "success" 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/AfrondingsTestBerekening.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.dsl.nl.grammar._ 4 | import org.scalarules.dsl.nl.grammar.AfrondingsTestBerekeningGlossary._ 5 | 6 | import scala.language.postfixOps 7 | 8 | class AfrondingsTestBerekening extends Berekening ( 9 | 10 | /* BigDecimal test calculations*/ 11 | Gegeven(afrondingsType is "halfNaarEven") Bereken 12 | afgerondBigDecimalHalfEven is (startBigDecimal halfNaarEven afgerond op 0 decimalen) 13 | , 14 | 15 | Gegeven(afrondingsType is "halfNaarNulToe") Bereken 16 | afgerondBigDecimalHalfNaarNulToe is (startBigDecimal halfNaarNulToe afgerond op 0 decimalen) 17 | , 18 | 19 | Gegeven(afrondingsType is "naarBeneden") Bereken 20 | afgerondBigDecimalNaarBeneden is (startBigDecimal naarBeneden afgerond op 0 decimalen) 21 | , 22 | 23 | Gegeven(afrondingsType is "naarBoven") Bereken 24 | afgerondBigDecimalNaarBoven is (startBigDecimal naarBoven afgerond op 0 decimalen) 25 | , 26 | 27 | Gegeven(afrondingsType is "naarNulToe") Bereken 28 | afgerondBigDecimalNaarNulToe is (startBigDecimal naarNulToe afgerond op 0 decimalen) 29 | , 30 | 31 | Gegeven(afrondingsType is "rekenkundig") Bereken 32 | afgerondBigDecimalRekenkundig is (startBigDecimal rekenkundig afgerond op 0 decimalen) 33 | , 34 | 35 | Gegeven(afrondingsType is "vanNulAf") Bereken 36 | afgerondBigDecimalVanNulAf is (startBigDecimal vanNulAf afgerond op 0 decimalen) 37 | , 38 | 39 | 40 | /* Percentage test calculations*/ 41 | Gegeven(afrondingsType is "halfNaarEven") Bereken 42 | afgerondPercentageHalfEven is (startPercentage halfNaarEven afgerond op 0 decimalen) 43 | , 44 | 45 | Gegeven(afrondingsType is "halfNaarNulToe") Bereken 46 | afgerondPercentageHalfNaarNulToe is (startPercentage halfNaarNulToe afgerond op 0 decimalen) 47 | , 48 | 49 | Gegeven(afrondingsType is "naarBeneden") Bereken 50 | afgerondPercentageNaarBeneden is (startPercentage naarBeneden afgerond op 0 decimalen) 51 | , 52 | 53 | Gegeven(afrondingsType is "naarBoven") Bereken 54 | afgerondPercentageNaarBoven is (startPercentage naarBoven afgerond op 0 decimalen) 55 | , 56 | 57 | Gegeven(afrondingsType is "naarNulToe") Bereken 58 | afgerondPercentageNaarNulToe is (startPercentage naarNulToe afgerond op 0 decimalen) 59 | , 60 | 61 | Gegeven(afrondingsType is "rekenkundig") Bereken 62 | afgerondPercentageRekenkundig is (startPercentage rekenkundig afgerond op 0 decimalen) 63 | , 64 | 65 | Gegeven(afrondingsType is "vanNulAf") Bereken 66 | afgerondPercentageVanNulAf is (startPercentage vanNulAf afgerond op 0 decimalen) 67 | , 68 | 69 | 70 | /* Bedrag test calculations*/ 71 | Gegeven(afrondingsType is "halfNaarEven") Bereken 72 | afgerondBedragHalfEven is (startBedrag halfNaarEven afgerond op 0 decimalen) 73 | , 74 | 75 | Gegeven(afrondingsType is "halfNaarNulToe") Bereken 76 | afgerondBedragHalfNaarNulToe is (startBedrag halfNaarNulToe afgerond op 0 decimalen) 77 | , 78 | 79 | Gegeven(afrondingsType is "naarBeneden") Bereken 80 | afgerondBedragNaarBeneden is (startBedrag naarBeneden afgerond op 0 decimalen) 81 | , 82 | 83 | Gegeven(afrondingsType is "naarBoven") Bereken 84 | afgerondBedragNaarBoven is (startBedrag naarBoven afgerond op 0 decimalen) 85 | , 86 | 87 | Gegeven(afrondingsType is "naarNulToe") Bereken 88 | afgerondBedragNaarNulToe is (startBedrag naarNulToe afgerond op 0 decimalen) 89 | , 90 | 91 | Gegeven(afrondingsType is "rekenkundig") Bereken 92 | afgerondBedragRekenkundig is (startBedrag rekenkundig afgerond op 0 decimalen) 93 | , 94 | 95 | Gegeven(afrondingsType is "vanNulAf") Bereken 96 | afgerondBedragVanNulAf is (startBedrag vanNulAf afgerond op 0 decimalen) 97 | 98 | ) 99 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/AfrondingsTestBerekeningGlossary.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.finance.nl.{Bedrag, Percentage} 4 | import org.scalarules.utils.Glossary 5 | 6 | object AfrondingsTestBerekeningGlossary extends Glossary { 7 | 8 | val afrondingsType = defineFact[String]() 9 | 10 | val startBigDecimal = defineFact[BigDecimal]() 11 | val afgerondBigDecimalHalfEven = defineFact[BigDecimal]() 12 | val afgerondBigDecimalHalfNaarNulToe = defineFact[BigDecimal]() 13 | val afgerondBigDecimalNaarBeneden = defineFact[BigDecimal]() 14 | val afgerondBigDecimalNaarBoven = defineFact[BigDecimal]() 15 | val afgerondBigDecimalNaarNulToe = defineFact[BigDecimal]() 16 | val afgerondBigDecimalRekenkundig = defineFact[BigDecimal]() 17 | val afgerondBigDecimalVanNulAf = defineFact[BigDecimal]() 18 | 19 | val startPercentage = defineFact[Percentage]() 20 | val afgerondPercentageHalfEven = defineFact[Percentage]() 21 | val afgerondPercentageHalfNaarNulToe = defineFact[Percentage]() 22 | val afgerondPercentageNaarBeneden = defineFact[Percentage]() 23 | val afgerondPercentageNaarBoven = defineFact[Percentage]() 24 | val afgerondPercentageNaarNulToe = defineFact[Percentage]() 25 | val afgerondPercentageRekenkundig = defineFact[Percentage]() 26 | val afgerondPercentageVanNulAf = defineFact[Percentage]() 27 | 28 | val startBedrag = defineFact[Bedrag]() 29 | val afgerondBedragHalfEven = defineFact[Bedrag]() 30 | val afgerondBedragHalfNaarNulToe = defineFact[Bedrag]() 31 | val afgerondBedragNaarBeneden = defineFact[Bedrag]() 32 | val afgerondBedragNaarBoven = defineFact[Bedrag]() 33 | val afgerondBedragNaarNulToe = defineFact[Bedrag]() 34 | val afgerondBedragRekenkundig = defineFact[Bedrag]() 35 | val afgerondBedragVanNulAf = defineFact[Bedrag]() 36 | 37 | } 38 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/ConditionsBerekening.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.dsl.nl.grammar.ConditionsBerekeningGlossary._ 4 | 5 | class ConditionsBerekening extends Berekening ( 6 | Gegeven(altijd) Bereken 7 | outputAlwaysAvailable is BigDecimal(10) 8 | , 9 | Gegeven(availableInput is aanwezig) Bereken 10 | outputShouldBeAvailableIfInputIsAvailable is BigDecimal(11) 11 | , 12 | Gegeven(unavailableInput is afwezig) Bereken 13 | outputShouldBeAvailableIfInputIsNotAvailable is BigDecimal(12) 14 | , 15 | Gegeven (altijd) 16 | Bereken 17 | tryToOverwriteThisValue is eerste (missingValueToUseInEerste, defaultValueForOverwriting) en 18 | defaultValueForOverwriting is BigDecimal(0) 19 | 20 | ) 21 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/ConditionsBerekeningGlossary.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.utils.Glossary 4 | 5 | object ConditionsBerekeningGlossary extends Glossary { 6 | val availableInput = defineFact[BigDecimal] 7 | val unavailableInput = defineFact[BigDecimal] 8 | 9 | val outputAlwaysAvailable = defineFact[BigDecimal] 10 | val outputShouldBeAvailableIfInputIsAvailable = defineFact[BigDecimal] 11 | val outputShouldBeAvailableIfInputIsNotAvailable = defineFact[BigDecimal] 12 | 13 | val tryToOverwriteThisValue = defineFact[BigDecimal] 14 | val defaultValueForOverwriting = defineFact[BigDecimal] 15 | val missingValueToUseInEerste = defineFact[BigDecimal] 16 | 17 | } 18 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/ConditionsBerekeningTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.dsl.nl.grammar.ConditionsBerekeningGlossary._ 4 | import org.scalarules.utils.InternalBerekeningenTester 5 | 6 | class ConditionsBerekeningTest extends InternalBerekeningenTester(new ConditionsBerekening) { 7 | 8 | test("of altijd condition werkt met lege context") gegeven ( 9 | 10 | ) verwacht ( 11 | outputAlwaysAvailable is BigDecimal(10) 12 | ) 13 | 14 | test("of altijd condition werkt met gevulde context") gegeven ( 15 | availableInput is BigDecimal(1) 16 | ) verwacht ( 17 | outputAlwaysAvailable is BigDecimal(10) 18 | ) 19 | 20 | test("of aanwezig condition werkt met lege context") gegeven ( 21 | 22 | ) verwacht ( 23 | outputAlwaysAvailable is BigDecimal(10), 24 | outputShouldBeAvailableIfInputIsAvailable niet aanwezig 25 | ) 26 | 27 | test("of aanwezig condition werkt met gevulde context") gegeven ( 28 | availableInput is BigDecimal(1) 29 | ) verwacht ( 30 | outputAlwaysAvailable is BigDecimal(10), 31 | outputShouldBeAvailableIfInputIsAvailable is BigDecimal(11) 32 | ) 33 | 34 | test("of niet aanwezig condition werkt met lege context") gegeven ( 35 | 36 | ) verwacht ( 37 | outputAlwaysAvailable is BigDecimal(10), 38 | outputShouldBeAvailableIfInputIsNotAvailable is BigDecimal(12) 39 | ) 40 | 41 | test("of niet aanwezig condition werkt met gevulde context") gegeven ( 42 | unavailableInput is BigDecimal(1) 43 | ) verwacht ( 44 | outputAlwaysAvailable is BigDecimal(10), 45 | outputShouldBeAvailableIfInputIsNotAvailable niet aanwezig 46 | ) 47 | 48 | test("of aanwezige waarde niet wordt overschreven") gegeven ( 49 | tryToOverwriteThisValue is BigDecimal(100) 50 | ) verwacht ( 51 | tryToOverwriteThisValue is BigDecimal(100) 52 | ) 53 | 54 | } 55 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/ConditionsTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import DslCondition.{andPredicate, orPredicate} 4 | import org.scalarules.engine._ 5 | import org.scalarules.facts.Fact 6 | import org.scalatest.{FlatSpec, Matchers} 7 | 8 | class ConditionsTestAndCondition extends FlatSpec with Matchers { 9 | 10 | it should "return false when given false and false" in 11 | assertTruthOfAndCondition(expected = false, inputA = false, inputB = false) 12 | 13 | it should "return false when given true and false" in 14 | assertTruthOfAndCondition(expected = false, inputA = true, inputB = false) 15 | 16 | it should "return false when given false and true" in 17 | assertTruthOfAndCondition(expected = false, inputA = false, inputB = true) 18 | 19 | it should "return true when given true and true" in 20 | assertTruthOfAndCondition(expected = true, inputA = true, inputB = true) 21 | 22 | private def assertTruthOfAndCondition(expected: Boolean, inputA: Boolean, inputB: Boolean): Unit = 23 | ConditionsTestHelper.assertConditionBasedMethod(andPredicate, expected, inputA, inputB) 24 | 25 | } 26 | 27 | 28 | class ConditionsTestOrCondition extends FlatSpec with Matchers { 29 | 30 | it should "return false when given false and false" in 31 | assertTruthOfOrCondition(expected = false, inputA = false, inputB = false) 32 | 33 | it should "return true when given false and true" in 34 | assertTruthOfOrCondition(expected = true, inputA = false, inputB = true) 35 | 36 | it should "return false when given true and false" in 37 | assertTruthOfOrCondition(expected = true, inputA = true, inputB = false) 38 | 39 | it should "return true when given true and true" in 40 | assertTruthOfOrCondition(expected = true, inputA = true, inputB = true) 41 | 42 | private def assertTruthOfOrCondition(expected: Boolean, inputA: Boolean, inputB: Boolean): Unit = 43 | ConditionsTestHelper.assertConditionBasedMethod(orPredicate, expected, inputA, inputB) 44 | 45 | } 46 | 47 | 48 | private object ConditionsTestHelper extends FlatSpec with Matchers { 49 | 50 | val context = Map.empty[Fact[_], Boolean] 51 | 52 | def assertConditionBasedMethod(f: (Condition, Condition) => Condition, expected: Boolean, inputA: Boolean, inputB: Boolean): Unit = 53 | f(_ => inputA, _ => inputB)(context) should be (expected) 54 | 55 | } 56 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/DslEvaluationTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.facts.SingularFact 4 | import org.scalarules.finance.nl.{Bedrag, Percentage} 5 | import org.scalatest.{FlatSpec, Matchers} 6 | 7 | class DslEvaluationTest extends FlatSpec with Matchers { 8 | 9 | val sutBedrag = new SingularFact[Bedrag]("testFactBedrag") 10 | val sutBigDecimal = new SingularFact[BigDecimal]("testFactBigDecimal") 11 | val sutString = new SingularFact[String]("testFactString") 12 | val sutPercentage = new SingularFact[Percentage]("testFactPercentage") 13 | 14 | it should "compile" in { 15 | -sutBedrag 16 | sutBedrag + sutBedrag 17 | sutBedrag - sutBedrag 18 | sutBedrag / sutBedrag 19 | sutBedrag / sutBigDecimal 20 | sutBedrag * sutBigDecimal 21 | -sutBigDecimal 22 | sutBigDecimal + sutBigDecimal 23 | sutBigDecimal - sutBigDecimal 24 | sutBigDecimal * sutBedrag 25 | sutBigDecimal / sutBigDecimal 26 | sutBigDecimal * sutBigDecimal 27 | sutBigDecimal * sutPercentage 28 | sutPercentage * sutBigDecimal 29 | sutBedrag * sutPercentage 30 | sutPercentage * sutBedrag 31 | } 32 | 33 | it should "not compile" in { 34 | "-sutString" shouldNot compile 35 | "sutBedrag + sutString" shouldNot compile 36 | "sutBedrag + sutBigDecimal" shouldNot compile 37 | "sutBedrag - sutBigDecimal" shouldNot compile 38 | "sutBedrag * sutBedrag" shouldNot compile 39 | "sutBigDecimal + sutBedrag" shouldNot compile 40 | "sutBigDecimal - sutBedrag" shouldNot compile 41 | "-sutPercentage" shouldNot compile 42 | "sutPercentage + sutPercentage" shouldNot compile 43 | "sutPercentage - sutPercentage" shouldNot compile 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/DslListEvaluationTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.finance.nl.{Bedrag, Percentage} 4 | import org.scalarules.facts.{ListFact, SingularFact} 5 | import org.scalatest.{FlatSpec, Matchers} 6 | 7 | class DslListEvaluationTest extends FlatSpec with Matchers { 8 | 9 | val sutBedrag = new SingularFact[Bedrag]("testFactBedrag") 10 | val sutBedragen = new ListFact[Bedrag]("testFactBedragen") 11 | val sutBedragen2 = new ListFact[Bedrag]("testFactBedragen2") 12 | val sutBigDecimal = new SingularFact[BigDecimal]("testFactBigDecimal") 13 | val sutBigDecimals = new ListFact[BigDecimal]("testFactBigDecimals") 14 | val sutString = new SingularFact[String]("testFactString") 15 | val sutStrings = new ListFact[String]("testFactStrings") 16 | val sutPercentage = new SingularFact[Percentage]("testFactPercentage") 17 | val sutPercentages = new ListFact[Percentage]("testFactPercentages") 18 | 19 | it should "compile" in { 20 | -sutBedragen 21 | sutBedragen + sutBedragen 22 | sutBedragen - sutBedragen 23 | sutBedragen / sutBedragen 24 | 25 | sutBedragen + sutBedrag 26 | sutBedragen - sutBedrag 27 | sutBedragen / sutBedrag 28 | 29 | sutBedragen / sutBigDecimals 30 | sutBedragen * sutBigDecimals 31 | 32 | sutBedragen / sutBigDecimal 33 | sutBedragen * sutBigDecimal 34 | 35 | sutBedrag + sutBedragen 36 | sutBedrag - sutBedragen 37 | sutBedrag / sutBedragen 38 | 39 | sutBedrag / sutBigDecimals 40 | sutBedrag * sutBigDecimals 41 | 42 | -sutBigDecimals 43 | sutBigDecimals + sutBigDecimals 44 | sutBigDecimals - sutBigDecimals 45 | sutBigDecimals / sutBigDecimals 46 | sutBigDecimals * sutBigDecimals 47 | 48 | sutBigDecimals + sutBigDecimal 49 | sutBigDecimals - sutBigDecimal 50 | sutBigDecimals / sutBigDecimal 51 | sutBigDecimals * sutBigDecimal 52 | 53 | sutBigDecimals * sutBedragen 54 | 55 | sutBigDecimals * sutBedrag 56 | 57 | sutBigDecimal + sutBigDecimals 58 | sutBigDecimal - sutBigDecimals 59 | sutBigDecimal * sutBigDecimals 60 | sutBigDecimal / sutBigDecimals 61 | 62 | sutBigDecimal * sutBedragen 63 | } 64 | 65 | it should "not compile" in { 66 | "sutBedragen * sutBedragen" shouldNot compile 67 | "sutBedragen * sutBedrag" shouldNot compile 68 | "sutBedragen + sutBigDecimals" shouldNot compile 69 | "sutBedragen - sutBigDecimals" shouldNot compile 70 | "sutBedragen + sutBigDecimal" shouldNot compile 71 | "sutBedragen - sutBigDecimal" shouldNot compile 72 | "sutBedrag * sutBedragen" shouldNot compile 73 | "sutBedrag + sutBigDecimals" shouldNot compile 74 | "sutBedrag - sutBigDecimals" shouldNot compile 75 | "sutBigDecimals + sutBedragen" shouldNot compile 76 | "sutBigDecimals - sutBedragen" shouldNot compile 77 | "sutBigDecimals / sutBedragen" shouldNot compile 78 | "sutBigDecimals + sutBedrag" shouldNot compile 79 | "sutBigDecimals - sutBedrag" shouldNot compile 80 | "sutBigDecimal + sutBedragen" shouldNot compile 81 | "sutBigDecimal - sutBedragen" shouldNot compile 82 | "sutBigDecimals / sutBedrag" shouldNot compile 83 | "sutBigDecimal / sutBedragen" shouldNot compile 84 | "sutBedragen + sutString" shouldNot compile 85 | "sutBedragen + sutBigDecimal" shouldNot compile 86 | "sutBedragen - sutBigDecimal" shouldNot compile 87 | "sutBedragen * sutBedrag" shouldNot compile 88 | "sutBedragen + sutStrings" shouldNot compile 89 | "sutBedragen + sutBigDecimals" shouldNot compile 90 | "sutBedragen - sutBigDecimals" shouldNot compile 91 | "sutBedragen * sutBedragen" shouldNot compile 92 | "sutBigDecimals + sutBedrag" shouldNot compile 93 | "sutBigDecimals - sutBedrag" shouldNot compile 94 | "sutPercentages + sutPercentages" shouldNot compile 95 | "sutPercentages - sutPercentages" shouldNot compile 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/LijstBerekeningGlossary.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.utils.Glossary 4 | 5 | object LijstBerekeningGlossary extends Glossary { 6 | val LijstGefilterd = defineListFact[Int] 7 | val LijstGefilterdMetList = defineListFact[Int] 8 | val LijstOngefilterd = defineListFact[Int] 9 | val LijstGefilterdComplexObject = defineListFact[ComplexFilterObject] 10 | val LijstOngefilterdComplexObject = defineListFact[ComplexFilterObject] 11 | } 12 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/LijstFilterBerekeningTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.dsl.nl.grammar.LijstBerekeningGlossary._ 4 | import org.scalarules.utils.InternalBerekeningenTester 5 | 6 | class LijstFilterBerekeningTest extends InternalBerekeningenTester(new LijstFilter) { 7 | 8 | test("of filteren van lijst werkt") gegeven ( 9 | LijstOngefilterd is List(0, 1, 2, 3, 4, 5, 6) 10 | ) verwacht ( 11 | LijstGefilterd is List(1, 2, 3, 4) 12 | ) 13 | 14 | test("of filteren van lijst werkt met Sequence") gegeven ( 15 | LijstOngefilterd is List(0, 1, 2, 3, 4, 5, 6) 16 | ) verwacht ( 17 | LijstGefilterdMetList is List(1, 2, 3, 4) 18 | ) 19 | 20 | test("of filteren van lijst werkt met complexe objecten") gegeven ( 21 | LijstOngefilterdComplexObject is List(ComplexFilterObject(0), ComplexFilterObject(1), ComplexFilterObject(2), ComplexFilterObject(3), 22 | ComplexFilterObject(4), ComplexFilterObject(5)) 23 | ) verwacht ( 24 | LijstGefilterdComplexObject is List(ComplexFilterObject(3),ComplexFilterObject(4)) 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/LijstFilterBerekeningen.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.dsl.core.projections.{ProjectableListFields, ProjectedDslEvaluation} 4 | import org.scalarules.dsl.nl.grammar.LijstBerekeningGlossary._ 5 | import org.scalarules.dsl.nl.grammar.ComplexObjectProjections._ 6 | import org.scalarules.engine._ 7 | import org.scalarules.facts.ListFact 8 | 9 | import scala.language.implicitConversions 10 | 11 | class LijstFilter extends Berekening ( 12 | Gegeven (altijd) 13 | Bereken 14 | LijstGefilterd is (filter lijst LijstOngefilterd op (1,2,3,4)) en 15 | LijstGefilterdMetList is (filter lijst LijstOngefilterd op List(1,2,3,4)) en 16 | LijstGefilterdComplexObject is (filter lijst LijstOngefilterdComplexObject op LijstOngefilterdComplexObject.value van (3,4,6)) 17 | ) 18 | 19 | case class ComplexFilterObject(value: Int) 20 | class ComplexObjectProjections(complexObjectFact: ListFact[ComplexFilterObject]) extends ProjectableListFields[ComplexFilterObject] { 21 | def outerFact: ListFact[ComplexFilterObject] = complexObjectFact 22 | 23 | val value: ProjectedDslEvaluation[ComplexFilterObject, Int] = projectField(_.value) 24 | } 25 | object ComplexObjectProjections { 26 | implicit def toProjection(f: ListFact[ComplexFilterObject]): ComplexObjectProjections = new ComplexObjectProjections(f) 27 | } 28 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/LoopBerekening.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import LoopBerekeningGlossary._ 4 | import org.scalarules.dsl.nl.grammar.meta.BerekeningReferentie 5 | 6 | class LoopBerekening extends Berekening ( 7 | Gegeven(altijd) Bereken 8 | simpleLoopResult bevat resultaten van SimpeleLoopElementBerekening over loopInput 9 | , 10 | Gegeven(altijd) Bereken 11 | nestedTestOutput bevat resultaten van GenesteLoopElementBerekening over nestedTestInput 12 | , 13 | Gegeven(altijd) Bereken 14 | filteredLoopResult bevat resultaten van GefilterdeLoopElementBerekening over loopInput 15 | ) 16 | 17 | @BerekeningReferentie 18 | class SimpeleLoopElementBerekening extends ElementBerekening[BigDecimal, BigDecimal] ( 19 | Invoer is innerLoopIteratee, 20 | Uitvoer is innerLoopReturnValue, 21 | Gegeven (altijd) Bereken innerLoopReturnValue is innerLoopIteratee + innerLoopAdditionValue 22 | ) 23 | 24 | @BerekeningReferentie 25 | class GenesteLoopElementBerekening extends ElementBerekening[List[BigDecimal], List[BigDecimal]] ( 26 | Invoer is nestedOuterLoopInput, 27 | Uitvoer is nestedOuterLoopResult, 28 | Gegeven (altijd) Bereken nestedOuterLoopResult bevat resultaten van SimpeleLoopElementBerekening over nestedOuterLoopInput 29 | ) 30 | 31 | @BerekeningReferentie 32 | class GefilterdeLoopElementBerekening extends ElementBerekening[BigDecimal, BigDecimal] ( 33 | Invoer is innerLoopIteratee, 34 | Uitvoer is innerLoopReturnValue, 35 | Gegeven (innerLoopIteratee is 2) Bereken innerLoopReturnValue is innerLoopIteratee + BigDecimal(2) 36 | ) 37 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/LoopBerekeningGlossary.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.utils.Glossary 4 | 5 | object LoopBerekeningGlossary extends Glossary { 6 | val loopInput = defineListFact[BigDecimal] 7 | val nestedTestInput = defineListFact[List[BigDecimal]] 8 | 9 | val innerLoopIteratee = defineFact[BigDecimal] 10 | val innerLoopAdditionValue = defineFact[BigDecimal] 11 | val innerLoopReturnValue = defineFact[BigDecimal] 12 | 13 | val nestedOuterLoopInput = defineListFact[BigDecimal] 14 | val nestedOuterLoopResult = defineListFact[BigDecimal] 15 | 16 | val simpleLoopResult = defineListFact[BigDecimal] 17 | val filteredLoopResult = defineListFact[BigDecimal] 18 | val nestedTestOutput = defineListFact[List[BigDecimal]] 19 | 20 | } 21 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/dsl/nl/grammar/LoopBerekeningTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.dsl.nl.grammar 2 | 3 | import org.scalarules.dsl.nl.grammar.LoopBerekeningGlossary._ 4 | import org.scalarules.utils.{InternalBerekeningenTester, lijst} 5 | 6 | class LoopBerekeningTest extends InternalBerekeningenTester(new LoopBerekening) { 7 | 8 | test("of simpele loop over lijst werkt") gegeven ( 9 | loopInput is List[BigDecimal](0, 1, 2, 3, 4, 5, 6), 10 | innerLoopAdditionValue is BigDecimal(2) 11 | ) verwacht ( 12 | simpleLoopResult is List[BigDecimal](2, 3, 4, 5, 6, 7, 8) 13 | ) 14 | 15 | test("of loop goed omgaat met niet aanwezige innerloop waardes") gegeven ( 16 | loopInput is List[BigDecimal](0, 1, 2, 3, 4, 5, 6) 17 | ) verwacht ( 18 | simpleLoopResult is List() 19 | ) 20 | 21 | test("of loop met geneste elementberekeningen werkt") gegeven ( 22 | nestedTestInput is List(List[BigDecimal](0, 1), List[BigDecimal](2, 3), List[BigDecimal](4, 5)), 23 | innerLoopAdditionValue is BigDecimal(1) 24 | ) verwacht ( 25 | nestedTestOutput is List(List[BigDecimal](1, 2), List[BigDecimal](3, 4), List[BigDecimal](5, 6)) 26 | ) 27 | 28 | test("of loop met inner loop goed omgaat met niet aanwezige innerloop waardes") gegeven ( 29 | nestedTestInput is List(List[BigDecimal](0, 1), List[BigDecimal](2, 3), List[BigDecimal](4, 5)) 30 | ) verwacht ( 31 | nestedTestOutput is List(List(), List(), List()) 32 | ) 33 | 34 | test("of loop goed omgaat met gefilterde return values") gegeven ( 35 | loopInput is List[BigDecimal](0, 1, 2) 36 | ) verwacht ( 37 | filteredLoopResult is List[BigDecimal](4) 38 | ) 39 | 40 | } 41 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/service/dsl/BusinessServiceTestBerekeningen.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.service.dsl 2 | 3 | import org.scalarules.dsl.nl.grammar._ 4 | import org.scalarules.service.dsl.BusinessServiceTestGlossary1._ 5 | import org.scalarules.service.dsl.BusinessServiceTestGlossary2._ 6 | import org.scalarules.service.dsl.BusinessServiceTestGlossary3._ 7 | import org.scalarules.service.dsl.NotABusinessServiceTestGlossary1._ 8 | import scala.language.postfixOps 9 | 10 | 11 | class BusinessServiceTestBerekening1 extends Berekening ( 12 | 13 | Gegeven(altijd) Bereken 14 | uitvoer1 is (verplichteInvoer1 + optioneleInvoer1) 15 | 16 | ) 17 | 18 | class BusinessServiceTestBerekening2 extends Berekening ( 19 | 20 | Gegeven(altijd) Bereken 21 | uitvoer2 is (verplichteInvoer2 + optioneleInvoer2) 22 | 23 | ) 24 | 25 | class BusinessServiceTestBerekening3 extends Berekening ( 26 | 27 | Gegeven(altijd) Bereken 28 | uitvoer3 is (verplichteInvoer3 + optioneleInvoer3) 29 | 30 | ) 31 | 32 | class NotABusinessServiceTestBerekening1 extends Berekening ( 33 | 34 | Gegeven(altijd) Bereken 35 | notABusinessServiceUitvoer is (notABusinessServiceVerplichtFact + notABusinessServiceOptioneelFact) 36 | 37 | ) 38 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/service/dsl/BusinessServiceTestGlossaries.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.service.dsl 2 | 3 | import org.scalarules.finance.nl.Bedrag 4 | import org.scalarules.utils.Glossary 5 | 6 | object BusinessServiceTestGlossary1 extends Glossary { 7 | val verplichteInvoer1 = defineFact[Bedrag] 8 | val optioneleInvoer1 = defineFact[Bedrag] 9 | val uitvoer1 = defineFact[Bedrag] 10 | } 11 | 12 | object BusinessServiceTestGlossary2 extends Glossary { 13 | val verplichteInvoer2 = defineFact[Bedrag] 14 | val optioneleInvoer2 = defineFact[Bedrag] 15 | val uitvoer2 = defineFact[Bedrag] 16 | } 17 | 18 | object BusinessServiceTestGlossary3 extends Glossary { 19 | val verplichteInvoer3 = defineFact[Bedrag] 20 | val optioneleInvoer3 = defineFact[Bedrag] 21 | val uitvoer3 = defineFact[Bedrag] 22 | } 23 | 24 | object NotABusinessServiceTestGlossary1 extends Glossary { 25 | val notABusinessServiceVerplichtFact = defineFact[Bedrag] 26 | val notABusinessServiceOptioneelFact = defineFact[Bedrag] 27 | val notABusinessServiceUitvoer = defineFact[Bedrag] 28 | } -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/utils/InternalHeuristicTester.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.utils 2 | 3 | import org.scalarules.dsl.nl.grammar.Berekening 4 | import org.scalarules.engine.{Context, FactEngine} 5 | import org.scalarules.facts.Fact 6 | import org.scalarules.services._ 7 | 8 | //scalastyle:off null 9 | 10 | class InternalHeuristicTester(heuristicService: HeuristicService, verplichteBerekening: Berekening, optioneleBerekeningen: Berekening*) 11 | extends InternalBerekeningenTester(verplichteBerekening, optioneleBerekeningen:_*) { 12 | 13 | override def runTest(description: String, context: Context, factValues: Seq[FactValues]): Unit = it should description in { 14 | val heuristicResult: Option[AnalyzedScenario] = heuristicService.runHeuristics(context, FactEngine.runNormalDerivations(_, berekeningen)) 15 | 16 | if (factValues == null) { 17 | withClue("Expecting to find no resulting scenario, but a result was returned: ") { 18 | heuristicResult should be(None) 19 | } 20 | } else { 21 | withClue("Expecting to find a resulting scenario, but none was found: ") { 22 | heuristicResult should not be (None) 23 | } 24 | 25 | val verwachteWaardes: Seq[(Fact[Any], Any)] = factValues flatMap (_.tuples) 26 | verwachteWaardes foreach { factValue => assert(heuristicResult.get.result, factValue._1, factValue._2) } 27 | } 28 | } 29 | 30 | override def debugTest(description: String, context: Context, factValues: Seq[FactValues]): Unit = it should description in { 31 | val heuristicResult: Option[AnalyzedScenario] = heuristicService.runHeuristics(context, c => { 32 | val debugResult = FactEngine.runDebugDerivations(c, berekeningen) 33 | println(PrettyPrinter.printSteps(debugResult._2)) 34 | debugResult._1 35 | }) 36 | 37 | if (factValues == null) 38 | heuristicResult should be (None) 39 | else { 40 | heuristicResult should not be (None) 41 | 42 | val verwachteWaardes: Seq[(Fact[Any], Any)] = factValues flatMap (_.tuples) 43 | verwachteWaardes foreach { factValue => assert(heuristicResult.get.result, factValue._1, factValue._2) } 44 | } 45 | } 46 | 47 | implicit class ExtendedResultOfGegeven(resultOfGegeven: ResultOfGegeven) { 48 | def verwachtGeenResultaat(): Unit = resultOfGegeven.tester.runTest(resultOfGegeven.description, resultOfGegeven.context, null) 49 | def debugGeenResultaat(): Unit = resultOfGegeven.tester.debugTest(resultOfGegeven.description, resultOfGegeven.context, null) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/utils/InternalTestDsl.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.utils 2 | 3 | import org.scalarules.finance.nl.{Bedrag, Per} 4 | import org.scalarules.dsl.nl.grammar.{Aanwezigheid, Berekening} 5 | import org.scalarules.engine.{Context, FactEngine} 6 | import org.scalarules.facts.Fact 7 | import org.scalatest.{FlatSpec, Matchers} 8 | 9 | // TODO : Make into English and move the Dutch specific parts to a Dutch dsl package 10 | class InternalBerekeningenTester(verplichteBerekening: Berekening, optioneleBerekeningen: Berekening*) extends FlatSpec with Matchers { 11 | 12 | val berekeningen = (verplichteBerekening :: optioneleBerekeningen.toList).flatMap(_.berekeningen) 13 | def waardes(factValues: FactValues*): FactValues = FactValues( factValues flatMap (_.tuples) ) 14 | def test(description: String): ResultOfTest = new ResultOfTest(description, this) 15 | 16 | def runTest(description: String, context: Context, factValues: Seq[FactValues]): Unit = it should description in { 17 | val result = FactEngine.runNormalDerivations(context, berekeningen) 18 | 19 | val verwachteWaardes: Seq[(Fact[Any], Any)] = factValues flatMap (_.tuples) 20 | verwachteWaardes foreach { factValue => assert(result, factValue._1, factValue._2) } 21 | } 22 | 23 | def debugTest(description: String, context: Context, factValues: Seq[FactValues]): Unit = it should description in { 24 | val result = FactEngine.runDebugDerivations(context, berekeningen) 25 | println(PrettyPrinter.printSteps(result._2)) 26 | 27 | val verwachteWaardes: Seq[(Fact[Any], Any)] = factValues flatMap (_.tuples) 28 | verwachteWaardes foreach { factValue => assert(result._1, factValue._1, factValue._2) } 29 | } 30 | 31 | protected def assert[A](result: Context, fact: Fact[A], value: A) = { 32 | fact.toEval(result) match { 33 | case Some(x) if value == None => fail(s"Feit ${fact.name} wordt verwacht niet aanwezig te zijn, maar heeft waarde $x") 34 | case Some(x) => assertValue(x, value) 35 | case None if value == None => // What to do?? --> Nothing.. You interpret Nil as 'expected not to be present', which is exactly what the None result means 36 | case _ => fail(s"Feit ${fact.name} is niet beschikbaar in het resultaat. Waarde $value werd verwacht") 37 | } 38 | } 39 | 40 | // TODO : Extract these into ValueInspectors which we can add new instances of to some map/list of inspectors (maybe even just integrate with ScalaTest) 41 | private def assertValue[A](actual: A, expected: A): Unit = { 42 | actual match { 43 | case x: List[Any] if expected != None => { 44 | assert(x.length == expected.asInstanceOf[List[Any]].length, "Lists sizes mismatch") 45 | x.zip(expected.asInstanceOf[List[Any]]).foreach( a => assertValue (a._1, a._2) ) 46 | } 47 | case x: BigDecimal if expected != None => x.setScale(24, BigDecimal.RoundingMode.HALF_EVEN) should be (expected.asInstanceOf[BigDecimal].setScale(24, BigDecimal.RoundingMode.HALF_EVEN)) 48 | case x: Bedrag if expected != None => x.afgerondOpCenten should be (expected.asInstanceOf[Bedrag].afgerondOpCenten) 49 | // TODO: This is a bit ugly ... maybe we have to create a Per-companion object with appropriate unapply methods 50 | case x: Per[_, _] if expected != None && x.waarde.isInstanceOf[Bedrag] => { 51 | val concreteExpected: Per[Bedrag, _] = expected.asInstanceOf[Per[Bedrag, _]] 52 | x.waarde.asInstanceOf[Bedrag].afgerondOpCenten should be (concreteExpected.waarde.afgerondOpCenten) 53 | x.termijn should be (concreteExpected.termijn) 54 | } 55 | case x: Any if expected != None => x should be (expected) 56 | } 57 | } 58 | 59 | implicit class FactToFactValues[A](fact: Fact[A]) { 60 | def is(value: A): FactValues = FactValues(List((fact, value))) 61 | def niet(aanwezigheid: Aanwezigheid): FactValues = FactValues(List((fact, None))) 62 | } 63 | } 64 | 65 | case class FactValues(tuples: Seq[(Fact[Any], Any)]) 66 | 67 | class ResultOfTest (description: String, tester: InternalBerekeningenTester) extends Matchers { 68 | def gegeven(factValues: FactValues*): ResultOfGegeven = new ResultOfGegeven(factValues.flatMap(_.tuples).toMap, description, tester) 69 | } 70 | 71 | class ResultOfGegeven(val context: Context, val description: String, val tester: InternalBerekeningenTester) { 72 | def verwacht(factValues: FactValues*): Unit = tester.runTest(description, context, factValues) 73 | def debug(factValues: FactValues*): Unit = tester.debugTest(description, context, factValues) 74 | } 75 | 76 | 77 | //scalastyle:off object.name 78 | object lijst { 79 | def van(lengte: Int): listIntermediateResult = new listIntermediateResult(lengte) 80 | 81 | class listIntermediateResult(lengte: Int) { 82 | def waarde[A](value: A) : List[A] = List.fill(lengte)(value) 83 | } 84 | } 85 | //scalastyle:on object.name 86 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/utils/InternalTestDslTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.utils 2 | 3 | import org.scalarules.finance.nl._ 4 | import org.scalarules.utils.TestDslTestGlossary.{BedragPerJaarA, BedragPerJaarB} 5 | 6 | class TestDslTest extends InternalBerekeningenTester(new TestDslTestBerekening) { 7 | 8 | test("omlaag afronden van Bedrag Per Jaar") gegeven ( 9 | BedragPerJaarA is ("50.123456789".euro per Jaar) 10 | ) verwacht ( 11 | BedragPerJaarB is ("50.12".euro per Jaar) 12 | ) 13 | 14 | test("omhoog afronden van Bedrag Per Jaar") gegeven ( 15 | BedragPerJaarA is ("50.126456789".euro per Jaar) 16 | ) verwacht ( 17 | BedragPerJaarB is ("50.13".euro per Jaar) 18 | ) 19 | 20 | test("niet afronden van Bedrag Per Jaar") gegeven ( 21 | BedragPerJaarA is ("50.12".euro per Jaar) 22 | ) verwacht ( 23 | BedragPerJaarB is ("50.12".euro per Jaar) 24 | ) 25 | 26 | } 27 | 28 | object TestDslTestGlossary extends Glossary { 29 | val BedragPerJaarA = defineFact[Bedrag Per Jaar] 30 | val BedragPerJaarB = defineFact[Bedrag Per Jaar] 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/utils/InternalTestUtils.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.utils 2 | 3 | import org.scalarules.derivations.Derivation 4 | import org.scalarules.engine.{FactEngine, Step, _} 5 | import org.scalarules.facts.Fact 6 | import org.scalarules.services.{AnalyzedScenario, HeuristicService} 7 | 8 | object InternalTestUtils { 9 | 10 | def run(initial: Context, derivations: List[Derivation]): Context = FactEngine.runNormalDerivations(initial, derivations) 11 | def run(initial: Context, derivations: List[Derivation], heuristicService: HeuristicService): Option[AnalyzedScenario] = heuristicService.runHeuristics(initial, run(_, derivations)) 12 | def debug(initial: Context, derivations: List[Derivation]): Context = { 13 | val result: (Context, List[Step]) = FactEngine.runDebugDerivations(initial, derivations) 14 | println(PrettyPrinter.printSteps(result._2)) 15 | result._1 16 | } 17 | def debug(initial: Context, derivations: List[Derivation], heuristicService: HeuristicService): Option[AnalyzedScenario] = heuristicService.runHeuristics(initial, debug(_, derivations)) 18 | 19 | 20 | def runAndExtractFact[A](initial: Context, derivations: List[Derivation], extract: Fact[A]): Option[A] = { 21 | extract.toEval( run(initial, derivations) ) 22 | } 23 | 24 | def debugAndExtractFact[A](initial: Context, derivations: List[Derivation], extract: Fact[A]): Option[A] = { 25 | extract.toEval( debug(initial, derivations) ) 26 | } 27 | 28 | def runAndExtractFact[A](initial: Context, derivations: List[Derivation], heuristicService: HeuristicService, extract: Fact[A]): Option[A] = { 29 | extract.toEval( run(initial, derivations, heuristicService).get.result ) 30 | } 31 | 32 | def debugAndExtractFact[A](initial: Context, derivations: List[Derivation], heuristicService: HeuristicService, extract: Fact[A]): Option[A] = { 33 | extract.toEval( debug(initial, derivations, heuristicService).get.result ) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/utils/MacroGlossaryTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.utils 2 | 3 | import org.scalarules.facts.{ListFact, SingularFact} 4 | import org.scalatest.{FlatSpec, Matchers} 5 | 6 | import scala.language.reflectiveCalls 7 | 8 | class MacroGlossaryTest extends FlatSpec with Matchers { 9 | 10 | it should "work with macros to define facts" in { 11 | 12 | val firstDescription = "First fact" 13 | val secondDescription = "Second fact" 14 | val thirdDescription = "Third fact" 15 | val fourthDescription = "Fourth fact" 16 | 17 | 18 | val g = new Glossary { 19 | val factA = defineFact[String](firstDescription) 20 | val factB = defineFact[String](secondDescription) 21 | val factC = defineListFact[String](thirdDescription) 22 | val factD = defineListFact[String](fourthDescription) 23 | } 24 | 25 | g.factA.name should be("factA") 26 | g.factB.name should be("factB") 27 | g.factC.name should be("factC") 28 | g.factD.name should be("factD") 29 | 30 | g.factA.isInstanceOf[SingularFact[String]] should be(true) 31 | g.factB.isInstanceOf[SingularFact[String]] should be(true) 32 | g.factC.isInstanceOf[ListFact[String]] should be(true) 33 | g.factD.isInstanceOf[ListFact[String]] should be(true) 34 | 35 | g.facts.size should be(4) 36 | 37 | g.facts.get("factA").get should be(g.factA) 38 | g.facts.get("factB").get should be(g.factB) 39 | g.facts.get("factC").get should be(g.factC) 40 | g.facts.get("factD").get should be(g.factD) 41 | 42 | g.facts.get("factA").get.description should be(firstDescription) 43 | g.facts.get("factB").get.description should be(secondDescription) 44 | g.facts.get("factC").get.description should be(thirdDescription) 45 | g.facts.get("factD").get.description should be(fourthDescription) 46 | } 47 | 48 | it should "store concrete value type in the Fact on creation" in { 49 | val g = new Glossary { 50 | val stringFact = defineFact[String] 51 | val intFact = defineFact[Int] 52 | val intListFact = defineListFact[Int] 53 | } 54 | 55 | g.stringFact.valueType should be("String") 56 | g.intFact.valueType should be("Int") 57 | g.intListFact.valueType should be("Int") 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /engine/src/test/scala/org/scalarules/utils/TestDslTestBerekening.scala: -------------------------------------------------------------------------------- 1 | package org.scalarules.utils 2 | 3 | import org.scalarules.dsl.nl.grammar._ 4 | import org.scalarules.utils.TestDslTestGlossary._ 5 | 6 | class TestDslTestBerekening extends Berekening ( 7 | Gegeven(altijd) 8 | Bereken 9 | BedragPerJaarB is BedragPerJaarA 10 | ) 11 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.11 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.8.0") 2 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.3.5") 3 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") 4 | -------------------------------------------------------------------------------- /project/scalastyle-config.xml: -------------------------------------------------------------------------------- 1 | 2 | Rabobank standard configuration 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | true 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /project/scalastyle-test-config.xml: -------------------------------------------------------------------------------- 1 | 2 | Rabobank test configuration 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | true 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | --------------------------------------------------------------------------------