bali-component-framework
Version:
This library provides a JavaScript based implementation of the Bali Nebula™ Component Framework.
767 lines (600 loc) • 20.4 kB
JavaScript
/************************************************************************
* 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 abstract class defines the invariant methods that all visitors must support.
*/
const moduleName = '/bali/abstractions/Visitor';
const utilities = require('../utilities');
const Component = require('./Component').Component;
const Exception = require('./Component').Exception;
/**
* This constructor creates a new visitor component that can be used to visit components.
*
* It provides implementations of each method that by default just traverse the
* parse tree. Subclasses should override most of the methods.
*
* 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 {Array} ancestry An array of type names that make up the ancestry for the visitor.
* @returns {Visitor} The new visitor.
*/
const Visitor = function(ancestry, debug) {
Component.call(
this,
ancestry.concat(moduleName),
[],
undefined, // must be undefined to avoid infinite loop
debug
);
this.depth = 0;
return this;
};
Visitor.prototype = Object.create(Component.prototype);
Visitor.prototype.constructor = Visitor;
exports.Visitor = Visitor;
// PUBLIC METHODS
// acceptClause: 'accept' expression
Visitor.prototype.visitAcceptClause = function(node) {
const message = node.getItem(1);
message.acceptVisitor(this);
};
// angle: ANGLE
Visitor.prototype.visitAngle = function(angle) {
this.visitElement(angle);
};
// arguments:
// expression (',' expression)* |
// /* no expressions */
Visitor.prototype.visitArguments = function(node) {
this.depth++;
const iterator = node.getIterator();
while (iterator.hasNext()) {
const item = iterator.getNext();
item.acceptVisitor(this);
}
this.depth--;
};
// arithmeticExpression: expression ('*' | '/' | '//' | '+' | '-') expression
Visitor.prototype.visitArithmeticExpression = function(node) {
var operand = node.getItem(1);
operand.acceptVisitor(this);
var operator = node.operator;
operand = node.getItem(2);
operand.acceptVisitor(this);
};
// association: element ':' expression
Visitor.prototype.visitAssociation = function(association) {
association.getKey().acceptVisitor(this);
association.getValue().acceptVisitor(this);
};
// attribute: variable '[' indices ']'
Visitor.prototype.visitAttribute = function(node) {
const variable = node.getItem(1);
variable.acceptVisitor(this);
const indices = node.getItem(2);
indices.acceptVisitor(this);
};
// attributeExpression: expression '[' indices ']'
Visitor.prototype.visitAttributeExpression = function(node) {
const expression = node.getItem(1);
expression.acceptVisitor(this);
const indices = node.getItem(2);
indices.acceptVisitor(this);
};
// binary: BINARY
Visitor.prototype.visitBinary = function(binary) {
this.visitElement(binary);
};
// boolean: 'false' | 'true'
Visitor.prototype.visitBoolean = function(boolean) {
this.visitElement(boolean);
};
// block: '{' code '}'
Visitor.prototype.visitBlock = function(node) {
const code = node.getItem(1);
code.acceptVisitor(this);
};
// breakClause: 'break' 'loop'
Visitor.prototype.visitBreakClause = function(node) {
};
Visitor.prototype.visitCanonicalComparator = function(comparator) {
};
// catalog:
// association (',' association)* |
// EOL (association EOL)* |
// ':' /* no associations */
Visitor.prototype.visitCatalog = function(catalog) {
this.visitCollection(catalog);
};
// checkoutClause: 'checkout' recipient ('at level' expression)? 'from' expression;
Visitor.prototype.visitCheckoutClause = function(node) {
const iterator = node.getIterator();
while (iterator.hasNext()) {
const item = iterator.getNext();
item.acceptVisitor(this);
}
};
// code:
// statement (';' statement)* |
// EOL (statement EOL)* |
// /* no statements */
Visitor.prototype.visitCode = function(node) {
this.depth++;
const iterator = node.getIterator();
while (iterator.hasNext()) {
const statement = iterator.getNext();
statement.acceptVisitor(this);
}
this.depth--;
};
// collection: range | list | catalog
Visitor.prototype.visitCollection = function(collection) {
if (collection.getType() === '/bali/collections/Range') {
const first = collection.getFirst();
if (first !== undefined) {
first.acceptVisitor(this);
}
const connector = collection.getConnector();
const last = collection.getLast();
if (last !== undefined) {
last.acceptVisitor(this);
}
} else if (collection.getSize() > 0) {
this.depth++;
const iterator = collection.getIterator();
while (iterator.hasNext()) {
const item = iterator.getNext();
item.acceptVisitor(this);
}
this.depth--;
}
};
Visitor.prototype.visitCollectionIterator = function(iterator) {
this.visitIterator(iterator);
};
// comment: NOTE | COMMENT
Visitor.prototype.visitComment = function(node) {
}
// comparisonExpression: expression ('<' | '=' | '>' | 'IS' | 'MATCHES') expression
Visitor.prototype.visitComparisonExpression = function(node) {
var operand = node.getItem(1);
operand.acceptVisitor(this);
const operator = node.operator;
operand = node.getItem(2);
operand.acceptVisitor(this);
};
// complementExpression: 'NOT' expression
Visitor.prototype.visitComplementExpression = function(node) {
const operand = node.getItem(1);
operand.acceptVisitor(this);
};
// component: value parameters? note?
Visitor.prototype.visitComponent = function(component) {
const type = component.getType().split('/');
if (type[1] === 'bali') {
switch (type[2]) {
case 'abstractions':
case 'agents':
case 'collections':
case 'elements':
case 'strings':
case 'trees':
const functionName = 'visit' + type[3];
if (this[functionName]) {
// dispatch to the actual type handler
this[functionName](component);
return;
}
}
}
// dispatch to the typed catalog handler
this.visitCatalog(component);
};
// chainExpression: expression '&' expression
Visitor.prototype.visitChainExpression = function(node) {
var operand = node.getItem(1);
operand.acceptVisitor(this);
operand = node.getItem(2);
operand.acceptVisitor(this);
};
// continueClause: 'continue' 'loop'
Visitor.prototype.visitContinueClause = function(node) {
};
// defaultExpression: expression '?' expression
Visitor.prototype.visitDefaultExpression = function(node) {
const value = node.getItem(1);
value.acceptVisitor(this);
const defaultValue = node.getItem(2);
defaultValue.acceptVisitor(this);
};
// dereferenceExpression: '@' expression
Visitor.prototype.visitDereferenceExpression = function(node) {
const reference = node.getItem(1);
reference.acceptVisitor(this);
};
// discardClause: 'discard' expression
Visitor.prototype.visitDiscardClause = function(node) {
const document = node.getItem(1);
document.acceptVisitor(this);
};
// duration: DURATION
Visitor.prototype.visitDuration = function(duration) {
this.visitElement(duration);
};
// element:
// angle |
// binary |
// boolean |
// duration |
// moment |
// name |
// number |
// pattern |
// percentage |
// probability |
// resource |
// symbol |
// tag |
// text |
// version
Visitor.prototype.visitElement = function(element) {
const parameters = element.getParameters();
this.visitParameters(parameters); // process any parameters first
// then process the component itself
};
// evaluateClause: (recipient (':=' | '+=' | '-=' | '*='))? expression
Visitor.prototype.visitEvaluateClause = function(node) {
const iterator = node.getIterator();
while (iterator.hasNext()) {
const item = iterator.getNext();
item.acceptVisitor(this);
}
const operator = node.operator;
};
Visitor.prototype.visitException = function(exception) {
const attributes = exception.getAttributes();
attributes.acceptVisitor(this);
// Note: any cause has already been integrated into the trace attribute
const parameters = exception.getParameters();
parameters.acceptVisitor(this);
};
// exponentialExpression: <assoc=right> expression '^' expression
Visitor.prototype.visitExponentialExpression = function(node) {
var operand = node.getItem(1);
operand.acceptVisitor(this);
operand = node.getItem(2);
operand.acceptVisitor(this);
};
// factorialExpression: expression '!'
Visitor.prototype.visitFactorialExpression = function(node) {
const operand = node.getItem(1);
operand.acceptVisitor(this);
};
// function: IDENTIFIER
Visitor.prototype.visitFunction = function(node) {
};
// functionExpression: function '(' arguments ')'
Visitor.prototype.visitFunctionExpression = function(node) {
const functionName = node.getItem(1);
functionName.acceptVisitor(this);
const args = node.getItem(2);
args.acceptVisitor(this);
};
// handleClause: 'handle' symbol (('with' block) | ('matching' expression 'with' block)+);
Visitor.prototype.visitHandleClause = function(node) {
const iterator = node.getIterator();
// handle 'symbol'
var symbol = iterator.getNext();
symbol.acceptVisitor(this);
if (node.getSize() === 2) {
// handle default ('matching any') with block
const block = iterator.getNext();
block.acceptVisitor(this);
} else {
// handle matching pattern with blocks
while (iterator.hasNext()) {
const pattern = iterator.getNext();
pattern.acceptVisitor(this);
const block = iterator.getNext();
block.acceptVisitor(this);
}
}
};
// ifClause: 'if' expression 'then' block ('else' 'if' expression 'then' block)* ('else' block)?
Visitor.prototype.visitIfClause = function(node) {
// handle 'if then' block
var condition = node.getItem(1);
condition.acceptVisitor(this);
var block = node.getItem(2);
block.acceptVisitor(this);
// handle optional additional conditions
const size = node.getSize();
for (var i = 3; i <= size; i += 2) {
if (i === size) {
// handle last 'else' block
block = node.getItem(i);
block.acceptVisitor(this);
} else {
// handle 'else if then' block
condition = node.getItem(i);
condition.acceptVisitor(this);
block = node.getItem(i + 1);
block.acceptVisitor(this);
}
}
};
// indices: expression (',' expression)*
Visitor.prototype.visitIndices = function(node) {
this.depth++;
const iterator = node.getIterator();
while (iterator.hasNext()) {
const item = iterator.getNext();
item.acceptVisitor(this);
}
this.depth--;
};
// inversionExpression: ('-' | '/' | '*') expression
Visitor.prototype.visitInversionExpression = function(node) {
const operator = node.operator;
const operand = node.getItem(1);
operand.acceptVisitor(this);
};
Visitor.prototype.visitIterator = function(iterator) {
const sequence = iterator.getSequence();
sequence.acceptVisitor(this);
const slot = iterator.getSlot();
};
// list:
// expression (',' expression)* |
// EOL (expression EOL)* |
// /* no items */
Visitor.prototype.visitList = function(list) {
this.visitCollection(list);
};
// logicalExpression: expression ('AND' | 'SANS' | 'XOR' | 'OR') expression
Visitor.prototype.visitLogicalExpression = function(node) {
var operand = node.getItem(1);
operand.acceptVisitor(this);
const operator = node.operator;
operand = node.getItem(2);
operand.acceptVisitor(this);
};
// magnitudeExpression: '|' expression '|'
Visitor.prototype.visitMagnitudeExpression = function(node) {
const operand = node.getItem(1);
operand.acceptVisitor(this);
};
Visitor.prototype.visitMergeSorter = function(sorter) {
};
// message: IDENTIFIER
Visitor.prototype.visitMessage = function(node) {
};
// messageExpression: expression ('.' | '<-') message '(' arguments ')'
Visitor.prototype.visitMessageExpression = function(node) {
const target = node.getItem(1);
target.acceptVisitor(this);
const operator = node.operator;
const messageName = node.getItem(2);
messageName.acceptVisitor(this);
const args = node.getItem(3);
args.acceptVisitor(this);
};
// moment: MOMENT
Visitor.prototype.visitMoment = function(moment) {
this.visitElement(moment);
};
// name: NAME
Visitor.prototype.visitName = function(name) {
this.visitElement(name);
};
Visitor.prototype.visitNodeIterator = function(iterator) {
const exception = new Exception({
$module: moduleName,
$procedure: '$visitNodeIterator',
$exception: '$notSupported',
$text: '"This method is not supported for node iterators."'
}, undefined, this.debug);
throw exception;
};
// number:
// 'undefined' |
// 'infinity' |
// '∞' |
// real |
// imaginary |
// '(' real (',' imaginary | 'e^' angle 'i') ')'
Visitor.prototype.visitNumber = function(number) {
this.visitElement(number);
};
// parameters: '(' catalog ')'
Visitor.prototype.visitParameters = function(parameters) {
if (parameters && parameters.getSize() > 0) {
this.depth++;
const iterator = parameters.getIterator();
while (iterator.hasNext()) {
const association = iterator.getNext();
association.acceptVisitor(this);
}
this.depth--;
}
};
// pattern: 'none' | REGEX | 'any'
Visitor.prototype.visitPattern = function(pattern) {
this.visitElement(pattern);
};
// percentage: PERCENTAGE
Visitor.prototype.visitPercentage = function(percentage) {
this.visitElement(percentage);
};
// postClause: 'post' expression 'on' expression
Visitor.prototype.visitPostClause = function(node) {
const message = node.getItem(1);
message.acceptVisitor(this);
const queue = node.getItem(2);
queue.acceptVisitor(this);
};
// precedenceExpression: '(' expression ')'
Visitor.prototype.visitPrecedenceExpression = function(node) {
const expression = node.getItem(1);
expression.acceptVisitor(this);
};
// probability: FRACTION | '1.'
Visitor.prototype.visitProbability = function(probability) {
this.visitElement(probability);
};
// procedure: '{' code '}'
Visitor.prototype.visitProcedure = function(procedure) {
const parameters = procedure.getParameters();
this.visitParameters(parameters); // process any parameters first
const code = procedure.getCode();
code.acceptVisitor(this); // then process the code in the procedure
};
// publishClause: 'publish' expression
Visitor.prototype.visitPublishClause = function(node) {
const event = node.getItem(1);
event.acceptVisitor(this);
};
Visitor.prototype.visitQueue = function(queue) {
this.visitCollection(queue);
};
// range: expression? connector=('<..<' | '<..' | '..<' | '..') expression?;
Visitor.prototype.visitRange = function(range) {
this.visitCollection(range);
};
Visitor.prototype.visitRangeIterator = function(iterator) {
this.visitIterator(iterator);
};
// resource: RESOURCE
Visitor.prototype.visitResource = function(resource) {
this.visitElement(resource);
};
// rejectClause: 'reject' expression
Visitor.prototype.visitRejectClause = function(node) {
const message = node.getItem(1);
message.acceptVisitor(this);
};
// retrieveClause: 'retrieve' recipient 'from' expression
Visitor.prototype.visitRetrieveClause = function(node) {
const message = node.getItem(1);
message.acceptVisitor(this);
const queue = node.getItem(2);
queue.acceptVisitor(this);
};
// returnClause: 'return' expression?
Visitor.prototype.visitReturnClause = function(node) {
const iterator = node.getIterator();
while (iterator.hasNext()) {
iterator.getNext().acceptVisitor(this);
}
};
// saveClause: 'save' expression ('as' recipient)?
Visitor.prototype.visitSaveClause = function(node) {
const iterator = node.getIterator();
while (iterator.hasNext()) {
iterator.getNext().acceptVisitor(this);
}
};
// selectClause: 'select' expression 'from' (expression 'do' block)+ ('else' block)?
Visitor.prototype.visitSelectClause = function(node) {
// handle the selection
const value = node.getItem(1);
value.acceptVisitor(this);
// handle option blocks
var block;
const size = node.getSize();
for (var i = 2; i <= size; i += 2) {
if (i === size) {
// handle the last 'else' block
block = node.getItem(i);
block.acceptVisitor(this);
} else {
// handle the 'do' option block
const option = node.getItem(i);
option.acceptVisitor(this);
block = node.getItem(i + 1);
block.acceptVisitor(this);
}
}
};
Visitor.prototype.visitSet = function(set) {
this.visitCollection(set);
};
// notarizeClause: 'notarize' expression 'as' expression
Visitor.prototype.visitNotarizeClause = function(node) {
const component = node.getItem(1);
component.acceptVisitor(this);
const name = node.getItem(2);
name.acceptVisitor(this);
};
Visitor.prototype.visitStack = function(stack) {
this.visitCollection(stack);
};
// statement: comment | mainClause handleClause?
Visitor.prototype.visitStatement = function(node) {
const iterator = node.getIterator();
while (iterator.hasNext()) {
iterator.getNext().acceptVisitor(this);
}
};
Visitor.prototype.visitStringIterator = function(iterator) {
this.visitIterator(iterator);
};
// symbol: SYMBOL
Visitor.prototype.visitSymbol = function(symbol) {
this.visitElement(symbol);
};
// tag: TAG
Visitor.prototype.visitTag = function(tag) {
this.visitElement(tag);
};
// text: TEXT | NARRATIVE
Visitor.prototype.visitText = function(text) {
this.visitElement(text);
};
// throwClause: 'throw' expression
Visitor.prototype.visitThrowClause = function(node) {
const exception = node.getItem(1);
exception.acceptVisitor(this);
};
// variable: IDENTIFIER
Visitor.prototype.visitVariable = function(node) {
};
// version: VERSION
Visitor.prototype.visitVersion = function(version) {
this.visitElement(version);
};
// whileClause: 'while' expression 'do' block
Visitor.prototype.visitWhileClause = function(node) {
const condition = node.getItem(1);
condition.acceptVisitor(this);
const block = node.getItem(2);
block.acceptVisitor(this);
};
// withClause: 'with' ('each' symbol 'in')? expression 'do' block
Visitor.prototype.visitWithClause = function(node) {
const size = node.getSize();
if (size > 2) {
// handle symbol
const item = node.getItem(1);
item.acceptVisitor(this);
}
const collection = node.getItem(size - 1);
collection.acceptVisitor(this);
const block = node.getItem(size);
block.acceptVisitor(this);
};