-
-
Notifications
You must be signed in to change notification settings - Fork 236
/
SliderConstraint.scala
102 lines (91 loc) · 4.8 KB
/
SliderConstraint.scala
1
2
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
99
100
101
102
// (C) Uri Wilensky. https://github.com/NetLogo/NetLogo
package org.nlogo.agent
import org.nlogo.api.{ CompilerServices, LogoException, LogoThunkFactory, ReporterLogoThunk, ValueConstraint }
import org.nlogo.core.CompilerException
import scala.util.{ Failure, Success, Try }
object SliderConstraint {
case class Spec(fieldName: String, displayName:String)
val Min = Spec("minimumCode", "Minimum")
val Max = Spec("maximumCode", "Maximum")
val Inc = Spec("incrementCode", "Increment")
trait SliderConstraintException extends Exception { val spec: Spec }
class ConstraintCompilerException(val spec: Spec, ex: CompilerException) extends
CompilerException(ex.getMessage, ex.start, ex.end, ex.filename) with SliderConstraintException
class ConstraintRuntimeException(val spec:Spec, message: String) extends
RuntimeException("Constraint runtime error for " + spec.displayName + ": " + message) with SliderConstraintException
class ConstraintExceptionHolder(errors: List[SliderConstraintException]) extends Exception {
def getErrors: List[SliderConstraintException] = errors
}
@throws(classOf[ConstraintExceptionHolder])
def makeSliderConstraint(agent: Agent, minCode: String, maxCode: String, incCode: String, defValue: Double,
ownerName: String, thunkFactory: LogoThunkFactory, compiler: CompilerServices): SliderConstraint = {
abstract class ConstraintCompiler[T] {
def compile(code: String, spec: Spec): Either[SliderConstraintException, T]
def makeConstraint(minT: T, maxT: T, incT: T): SliderConstraint
def compileAll(minCode: String, maxCode: String, incCode: String): SliderConstraint = {
val (minT, maxT, incT) = (compile(minCode, Min), compile(maxCode, Max), compile(incCode, Inc))
val allErrors = List(minT, maxT, incT).filter(_.isLeft).map(_.left.get)
if (!allErrors.isEmpty) throw new ConstraintExceptionHolder(allErrors)
// no compilation errors, so return a new constraint.
makeConstraint(minT.right.get, maxT.right.get, incT.right.get)
}
}
object ConstantConstraintCompiler extends ConstraintCompiler[Double] {
def compile(code: String, spec: Spec) = {
val o: Object = compiler.readFromString(code)
if (!(o.isInstanceOf[Double])) Left(new ConstraintRuntimeException(spec, "constraint must be a number."))
else Right(o.asInstanceOf[Double])
}
def makeConstraint(minT: Double, maxT: Double, incT: Double) = new ConstantSliderConstraint(minT, maxT, incT)
}
object DynamicConstraintCompiler extends ConstraintCompiler[ReporterLogoThunk] {
def compile(code: String, spec: Spec) = {
try Right(thunkFactory.makeReporterThunk(code, "slider '" + ownerName + "' " + spec.displayName))
catch {case ex: CompilerException => Left(new ConstraintCompilerException(spec, ex))}
}
def makeConstraint(minT: ReporterLogoThunk, maxT: ReporterLogoThunk, incT: ReporterLogoThunk) =
new DynamicSliderConstraint(minT, maxT, incT)
}
val allConstants =
compiler.isConstant(minCode) && compiler.isConstant(maxCode) && compiler.isConstant(incCode)
val constraintCompiler = if (allConstants) ConstantConstraintCompiler else DynamicConstraintCompiler
val con: SliderConstraint = constraintCompiler.compileAll(minCode, maxCode, incCode)
con.defaultValue=defValue
con
}
}
abstract class SliderConstraint extends ValueConstraint {
def minimum: Try[Double]
def increment: Try[Double]
def maximum: Try[Double]
var defaultValue = World.Zero
def assertConstraint(o: Object): Unit = {
if (!(o.isInstanceOf[Double])) { throw new ValueConstraint.Violation("Value must be a number.") }
}
def coerceValue(o: Object): Object = if (o.isInstanceOf[Double]) o else defaultValue
}
case class ConstantSliderConstraint(min: Double, max: Double, inc: Double) extends SliderConstraint {
def minimum: Try[Double] = Success(min)
def increment: Try[Double] = Success(inc)
def maximum: Try[Double] = Success(max)
}
/**
* Constraint suitable for Slider variables. The various limits on the
* Slider value can be specified using a NetLogo reporter.
*/
class DynamicSliderConstraint(min: ReporterLogoThunk,
max: ReporterLogoThunk,
inc: ReporterLogoThunk) extends SliderConstraint {
import SliderConstraint._
override def minimum = get(Min, min)
override def maximum = get(Max, max)
override def increment = get(Inc, inc)
private def get(spec:Spec, thunk:ReporterLogoThunk): Try[Double] = {
thunk.call
.flatMap(res => Try(res.asInstanceOf[Double]))
.recoverWith {
case ex: ClassCastException => Failure(new ConstraintRuntimeException(spec, s"Must be a number"))
case ex: LogoException => Failure(new ConstraintRuntimeException(spec, ex.getMessage))
}
}
}