Category talk:Wren-lsystem

From Rosetta Code

Source code

/* Module "lsystem.wren" */

/* 
   Rule represents a production rule in an L-system i.e. predecessor -> successor mapping.
   Rule objects are immutable.
*/
class Rule {
    // Constructs a new Rule object from a predecessor and successor
    construct new(pred, succ) {
        if (!((pred is String) && pred.count == 1)) {
            Fiber.abort("Predecessor must be a single character string.")
        }
        if (!((succ is String) && succ.count > 0)) {
            Fiber.abort("Successor must be a non-empty string.")
        }
        _pred = pred
        _succ = succ
    }

    // Self-evident getter properties.
    pred { _pred }
    succ { _succ }

    // Returns a string representation of the current instance.
    toString { "{%(_pred), %(_succ)}" }
}

/* 
    LSystem represents a Lindenmayer L-system with deterministic rules.
    The only mutable field is 'angle'.
*/
class LSystem {
    // Performs an operation on each symbol in the symbols string.
    static execute(symbols, operations) {
        if (!((symbols is String) && symbols.count > 0)) {
            Fiber.abort("Symbols must be a non-empty string.")
        }
        if (!((operations is Map) && operations.count > 0)) {
            Fiber.abort("Operations must be a non-empty map of single character strings to functions.")
        }
        for (me in operations) {
            if (!((me.key is String) && me.key.count == 1)) {
                Fiber.abort("Operation keys must be single character strings.")
            }
            if (!((me.value is Fn) && me.value.arity == 0)) {
                Fiber.abort("Operation values must be parameterless functions.")
            }
        }
        for (c in symbols) {
            var op = operations[c]
            if (op) op.call()
        }
    }

    // Constructs a new LSystem object. Throws an error if axioms include undeclared symbols 
    // or rule predecessors are not variables.
    construct new(variables, constants, axiom, rules, angle) {
        if (!(variables is List) || !(constants is List) || !(rules is List)) {
            Fiber.abort("Variables/constants/rules must all be lists.")
        }
        var symbols = variables + constants
        for (symbol in symbols) {
            if (!((symbol is String) && symbol.count == 1)) {
                Fiber.abort("Variables/constants must all be single character strings.")
            }
        }
        if (!((axiom is String) && axiom.count > 0)) {
            Fiber.abort("Axiom must be a non-empty string.")
        }
        for (c in axiom) {
            if (!symbols.contains(c)) Fiber.abort("Axiom contains an undeclared symbol '%(c)'")
        }
        for (rule in rules) {
            if (!variables.contains(rule.pred)) {
                Fiber.abort("Rule predecessor '%(rule.pred)' is not a declared variable.")
            }
            for (c in rule.succ) {
                if (!symbols.contains(c)) Fiber.abort("'%(rule.succ)' contains an undeclared symbol '%(c)'")
            }
        }
        if (!(angle is Num)) Fiber.abort("Angle must a number of radians.")
        _variables = variables.toList
        _constants = constants.toList
        _axiom = axiom
        _rules = rules.toList
        _angle = angle
    }

    // Convenience method to construct an LSystem object with a zero angle.
    static new(variables, constants, axiom, rules) {
        return new(variables, constants, axiom, rules, 0)
    }

    // Getter and setter properties for 'angle' field
    angle     { _angle }
    angle=(a) {
        if (!(a is Num)) Fiber.abort("Angle must be a number of radians.")
        _angle = a
    }

    // Getter properties for other fields
    variables { _variables.toList }
    constants { _constants.toList }
    axiom     { _axiom }
    rules     { _rules.toList }

    // Private helper method which iterates the L-system just once starting from a given axiom
    // and returns the result.
    iterateOnce_(axiom) {
        var result = ""
        for (c in axiom) {
            if (_constants.contains(c)) {
                result = result + c
                continue
            }
            for (rule in _rules) {
                if (rule.pred == c) {
                    result = result + rule.succ
                    break
                }
            }
        }
        return result
    }

    // Iterates the L-system 'n' times, starting from the current instance's axiom
    // and returns the result.
    iterate(n) {
        var result = _axiom
        for (i in 0...n) result = iterateOnce_(result)
        return result
    }

    // As iterate(n) but returns a list of all intermediate steps.
    listSteps(n) {
        var result = _axiom
        var steps = [result]
        for (i in 1..n) {
            result = iterateOnce_(result)
            steps.add(result)
        }
        return steps
    }

    // Returns a string representation of the current instance.
    toString {
        return "
Variables = %(_variables)
Constants = %(_constants)
Axiom     = %(_axiom)
Rules     = %(_rules)
Angle     = %(_angle)
"
    }
}