UNPKG

bali-component-framework

Version:

This library provides a JavaScript based implementation of the Bali Nebula™ Component Framework.

337 lines (298 loc) 11.1 kB
/************************************************************************ * Copyright (c) Crater Dog Technologies(TM). All Rights Reserved. * ************************************************************************ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * * * This code is free software; you can redistribute it and/or modify it * * under the terms of The MIT License (MIT), as published by the Open * * Source Initiative. (See http://opensource.org/licenses/MIT) * ************************************************************************/ 'use strict'; /* * This collection class captures the state and methods associated with a range component. */ const moduleName = '/bali/collections/Range'; const utilities = require('../utilities'); const abstractions = require('../abstractions'); /** * This constructor creates a new range collection using the specified first and last values. * * An optional debug argument may be specified that controls the level of debugging that * should be applied during execution. The allowed levels are as follows: * <pre> * 0: no debugging is applied (this is the default value and has the best performance) * 1: log any exceptions to console.error before throwing them * 2: perform argument validation checks on each call (poor performance) * 3: log interesting arguments, states and results to console.log * </pre> * * @param {Element} first The first element in the range. * @param {String} connector The connector between the first and last values (default: '..'). * @param {Element} last The last element in the range. * @param {Object} parameters Optional parameters used to parameterize this collection. * @returns {Range} The new range. */ const Range = function(first, connector, last, parameters, debug) { abstractions.Collection.call( this, [ moduleName ], [ '/bali/interfaces/Sequential' ], parameters, debug ); if (this.debug > 1) { this.validateArgument('$Range', '$first', first, [ '/javascript/Undefined', '/javascript/String', '/javascript/Boolean', '/javascript/Number', '/bali/abstractions/Component' ]); this.validateArgument('$Range', '$connector', connector, [ '/javascript/Undefined', '/javascript/String' ]); this.validateArgument('$Range', '$last', last, [ '/javascript/Undefined', '/javascript/String', '/javascript/Boolean', '/javascript/Number', '/bali/abstractions/Component' ]); } this.setFirst = function(value) { if (this.debug > 1) { this.validateArgument('$setFirst', '$value', value, [ '/javascript/Undefined', '/javascript/String', '/javascript/Boolean', '/javascript/Number', '/bali/abstractions/Component' ]); } if (first === null) first = undefined; if (value !== undefined) first = this.componentize(value); }; this.getFirst = function() { return first; }; this.setLast = function(value) { if (this.debug > 1) { this.validateArgument('$setLast', '$value', value, [ '/javascript/Undefined', '/javascript/String', '/javascript/Boolean', '/javascript/Number', '/bali/abstractions/Component' ]); } if (last === null) last = undefined; if (value !== undefined) last = this.componentize(value); } this.getLast = function() { return last; }; this.getSize = function() { return size; }; this.getConnector = function() { return connector; }; this.isEnumerable = function() { return first && first.isInteger && last && last.isInteger; }; // private attributes this.setFirst(first); this.setLast(last); if (connector === null || connector === undefined) connector = '..'; var size = 0; // assume this range is innumerable if (this.isEnumerable()) { size = last.toInteger() - first.toInteger() + 1; if (connector.endsWith('<')) size--; if (connector.startsWith('<')) size--; if (size < 1) { const exception = new abstractions.Exception({ $module: moduleName, $procedure: '$Range', $exception: '$invalidSize', $range: this, $size: size, $text: '"An enumerable range must have a positive size."' }, undefined, this.debug); throw exception; } } return this; }; Range.prototype = Object.create(abstractions.Collection.prototype); Range.prototype.constructor = Range; exports.Range = Range; // PUBLIC METHODS /** * This method returns an array containing the items in this range. If * this range is not enumerable this method throws an exception. * * @returns {Array} An array containing the items in this range. */ Range.prototype.toArray = function() { if (this.isEnumerable()) { const iterator = this.getIterator(); const array = []; while (iterator.hasNext()) { array.push(iterator.getNext()); } return array; } const exception = new abstractions.Exception({ $module: moduleName, $procedure: '$toArray', $exception: '$notEnumerable', $range: this, $text: '"Only an enumerable range of integers may be represented as an array."' }, undefined, this.debug); throw exception; }; /** * This method returns an agent that can be used to iterate over the integers in * a range. If this range is not enumerable this method throws an exception. * * @returns {Iterator} An iterator for this range. */ Range.prototype.getIterator = function() { if (this.isEnumerable()) { const iterator = new RangeIterator(this, this.debug); return iterator; } const exception = new abstractions.Exception({ $module: moduleName, $procedure: '$getIterator', $exception: '$notEnumerable', $range: this, $text: '"Only an enumerable range of integers may be iterated over."' }, undefined, this.debug); throw exception; }; /** * This method returns the index of the specified item, or zero if it is not in * this range. If this range is not enumerable this method throws an exception. * * @param {Component} item The item to be indexed. * @returns {Number} The index of the specified item. */ Range.prototype.getIndex = function(item) { if (this.debug > 1) { this.validateArgument('$getIndex', '$item', item, [ '/javascript/Number', '/bali/elements/Number' ]); } if (this.isEnumerable()) { const size = this.getSize(); // will throw an exception if range is not enumerable const offset = this.componentize(item).toInteger(); var index = offset - this.getFirst().toInteger(); if (this.getConnector().startsWith('.')) { index++; } if (index < 0 || index > size) return 0; // not in the range return index; } const exception = new abstractions.Exception({ $module: moduleName, $procedure: '$getIndex', $exception: '$notEnumerable', $range: this, $text: '"Only an enumerable range of integers is indexed."' }, undefined, this.debug); throw exception; }; /** * This method retrieves the item that is associated with the specified index * from this collection. * * @param {Number} index The index of the desired item. * @returns {Component} The item at the position in this collection. */ Range.prototype.getItem = function(index) { if (this.debug > 1) { this.validateArgument('$getItem', '$index', index, [ '/javascript/Number' ]); } const iterator = this.getIterator(); iterator.toSlot(index); return iterator.getPrevious(); }; // PUBLIC FUNCTIONS /** * This function returns an effective range that takes into account the inclusivity of * the endpoints for the specified range. If the specified range is not enumerable this * function throws an exception. * * @param {Range} The specified range. * @param {Number} debug A number in the range 0..3. */ Range.effective = function(range, debug) { if (debug > 1) { abstractions.Component.validateArgument(moduleName, '$effective', '$range', range, [ moduleName, ]); } if (range.isEnumerable()) { const connector = range.getConnector(); if (connector === '..') return range; var first = range.getFirst().toInteger(); if (connector.startsWith('<')) first++; var last = range.getLast().toInteger(); if (connector.endsWith('<')) last--; return new Range(first, '..', last, range.getParameters(), debug); } const exception = new abstractions.Exception({ $module: moduleName, $procedure: '$effective', $exception: '$notEnumerable', $range: range, $text: '"Only an enumerable range of integers has an effective range."' }, undefined, this.debug); throw exception; }; // PRIVATE CLASSES const RangeIterator = function(range, debug) { abstractions.Iterator.call( this, ['/bali/collections/RangeIterator'], range, debug ); const size = range.getSize(); // will throw an exception if range is not enumerable var first = range.getFirst().toInteger(); var slot = 0; // the slot before the first integer this.getSlot = function() { return slot; }; this.toStart = function() { slot = 0; // the slot before the first integer }; this.toSlot = function(newSlot) { if (this.debug > 1) { this.validateArgument('$toSlot', '$newSlot', newSlot, [ '/javascript/Number' ]); } if (newSlot > size) newSlot = size; if (newSlot < -size) newSlot = -size; if (newSlot < 0) newSlot = newSlot + size + 1; slot = newSlot; }; this.toEnd = function() { slot = size; // the slot after the last integer }; this.hasPrevious = function() { return slot > 0; }; this.hasNext = function() { return slot < size; }; this.getPrevious = function() { if (!this.hasPrevious()) return; return range.componentize(--slot + first); }; this.getNext = function() { if (!this.hasNext()) return; return range.componentize(slot++ + first); }; return this; }; RangeIterator.prototype = Object.create(abstractions.Iterator.prototype); RangeIterator.prototype.constructor = RangeIterator;