summaryrefslogtreecommitdiff
path: root/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Evaluator.kt
blob: 78b98e709acc1754c7fcf9669a0b526550ec3e20 (plain)
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// SPDX-License-Identifier: BSD-3-Clause
package org.islandoftex.arara.model

import org.islandoftex.arara.Arara
import org.islandoftex.arara.configuration.AraraSpec
import org.islandoftex.arara.localization.LanguageController
import org.islandoftex.arara.localization.Messages
import org.islandoftex.arara.ruleset.Conditional
import org.islandoftex.arara.utils.Methods
import org.mvel2.templates.TemplateRuntime

/**
 * Implements the evaluator model, on which a conditional can be analyzed and
 * processed.
 *
 * @author Island of TeX
 * @version 5.0
 * @since 4.0
 */
class Evaluator {
    // this attribute holds the maximum number of
    // loops arara will accept; it's like
    // reaching infinity
    private val loops: Int = Arara.config[AraraSpec.Execution.maxLoops]

    // the counter for the current execution, it
    // helps us keep track of the number of times
    // this evaluation has happened, and also to
    // prevent potential infinite loops
    private var counter: Int = 0

    // a flag that indicates the
    // evaluation to halt regardless
    // of the the result
    private var halt: Boolean = false

    /**
     * Check if a condition is of type if or unless and whether halt
     * is set.
     * @param type The type to check.
     * @param haltCheck The value [halt] should have.
     * @return `(type == if || type == unless) && haltCheck`
     */
    private fun isIfUnlessAndHalt(
        type: Conditional.ConditionalType,
        haltCheck: Boolean = true
    ): Boolean =
            (type == Conditional.ConditionalType.IF ||
                    type == Conditional.ConditionalType.UNLESS) &&
                    halt == haltCheck

    /**
     * Only run the evaluation of the conditional including a check whether
     * the result needs to be inverted.
     * @param conditional The conditional.
     * @return The result of the evaluation.
     */
    @Throws(AraraException::class, RuntimeException::class)
    private fun evaluateCondition(conditional: Conditional): Boolean {
        val result = TemplateRuntime.eval("@{ " + conditional.condition + " }",
                Methods.getConditionalMethods())
        return if (result is Boolean) {
            if (conditional.type == Conditional.ConditionalType.UNLESS ||
                    conditional.type == Conditional.ConditionalType.UNTIL)
                !result
            else
                result
        } else {
            throw AraraException(messages.getMessage(
                    Messages.ERROR_EVALUATE_NOT_BOOLEAN_VALUE))
        }
    }

    /**
     * Evaluate the provided conditional.
     *
     * @param conditional The conditional object.
     * @return A boolean value indicating if the conditional holds.
     * @throws AraraException Something wrong happened, to be caught in the
     * higher levels.
     */
    @Throws(AraraException::class)
    @Suppress("TooGenericExceptionCaught")
    fun evaluate(conditional: Conditional): Boolean {
        // when in dry-run mode or not evaluating a
        // conditional, arara always ignores conditional
        // evaluations
        if (conditional.type == Conditional.ConditionalType.NONE ||
                Arara.config[AraraSpec.Execution.dryrun] ||
                isIfUnlessAndHalt(conditional.type, true))
            return false
        else if (isIfUnlessAndHalt(conditional.type, false)) {
            halt = true
        }

        // check counters and see if the execution
        // has reached our concept of infinity,
        // thus breaking the cycles
        counter++
        return when {
            conditional.type === Conditional.ConditionalType.WHILE
                    && counter > loops -> false
            conditional.type === Conditional.ConditionalType.UNTIL
                    && counter >= loops -> false
            else -> {
                try {
                    evaluateCondition(conditional)
                } catch (exception: RuntimeException) {
                    throw AraraException(messages.getMessage(Messages
                            .ERROR_EVALUATE_COMPILATION_FAILED),
                            exception)
                }
            }
        }
    }

    companion object {
        // the application messages obtained from the
        // language controller
        private val messages = LanguageController
    }
}