├── .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 |
--------------------------------------------------------------------------------