<commit_before>var sys = require('sys'),
	util = require('./util'),
	q = util.q,
	debug = util.debug

exports.parse = util.intercept('ParseError', doParse)

var L_PAREN = '(',
	R_PAREN = ')',
	L_CURLY = '{',
	R_CURLY = '}',
	L_ARRAY = '[',
	R_ARRAY = ']'

var gToken, gIndex, gTokens, gState, gAST

var ParseError = function(file, msg) {
	this.name = 'ParseError';
	this.message = ['on line', gToken.line + ',', 'column', gToken.column, 'of', '"'+file+'":', msg].join(' ')
}
ParseError.prototype = Error.protoype

function doParse(tokens) {
	gTokens = tokens
	gIndex = -1
	gToken = null
	gAST = []

	while (true) {
		if (gIndex + 1 == gTokens.length) { break }
		advance()
		gAST.push(parseStatement())
	}

	return gAST
}

var parseStatement = function() {
	switch(gToken.type) {
		case 'string':
		case 'number':
			return getStaticValue()
		case 'symbol':
			switch(gToken.value) {
				case '<': return parseXML()
				case '=': halt('Unexpected symbol "=". Did you forget a "let" at the beginning of the line?')
				default: halt('Unexpected symbol "'+gToken.value+'"')
			}
		case 'name':
			return parseAliasOrInvocation()
		case 'keyword':
			switch (gToken.value) {
				case 'import': return parseImport()
				case 'let': return parseDeclaration()
				case 'for': return parseForLoop()
				case 'if': return parseIfStatement()
				default: halt('Unexpected keyword "'+gToken.value+'" at the beginning of a statement')
			}
		default:
			halt('Unknown parse statement token: ' + gToken.type)
	}
}

function parseBlock(statementType) {
	advance('symbol', L_CURLY, 'beginning of the '+statementType+'\'s block')
	var block = []
	while(!isAhead('symbol', R_CURLY)) {
		advance()
		block.push(parseStatement())
	}
	advance('symbol', R_CURLY, 'end of the '+statementType+' statement\'s block')
	return block
}

/*******************
 * Utility methods *
 *******************/
var assert = function(ok, msg) { if (!ok) halt(msg) }
var halt = function(msg) {
	sys.puts(util.grabLine(gToken.file, gToken.line, gToken.column, gToken.span));
	throw new ParseError(gToken.file, msg)
}
var advance = function(type, value, expressionType) {
	var nextToken = gTokens[++gIndex]
	if (!nextToken) { halt('Unexpected end of file') }
	function check(v1, v2) {
		assert(v1 == v2,
			['Expected a', q(type),
				value ? 'of value ' + q(value) : '',
				expressionType ? 'for the ' + expressionType : '',
				'but found a', q(nextToken.type),
				'of value', q(nextToken.value)].join(' ')
	)}
	if (type) { check(findInArray(type, nextToken.type), nextToken.type) }
	if (value) { check(findInArray(value, nextToken.value), nextToken.value) }
	gToken = nextToken
}
var isAhead = function(type, value, steps) {
	var token = gTokens[gIndex + (steps || 1)]
	if (!token) { return false }
	if (type && findInArray(type, token.type) != token.type) { return false }
	if (value && findInArray(value, token.value) != token.value) { return false }
	return true
}
// Find an item in an array and return it
//  if target is in array, return target
//  if target is not in array, return array
//  if array is not an array, return array
var findInArray = function(array, target) {
	if (!(array instanceof Array)) { return array }
	for (var i=0, item; item = array[i]; i++) {
		if (item == target) { return item }
	}
	return array
}

function astGenerator(generatorFn) {
	return function() {
		var startToken = gToken,
			ast = generatorFn(),
			endToken = gToken

		ast.file = startToken.file
		ast.line = startToken.line
		ast.column = startToken.column
		ast.lineEnd = endToken.line
		ast.columnEnd = endToken.column
		if (ast.line == ast.lineEnd) {
			ast.span = endToken.column - startToken.column + endToken.span
		} else {
			ast.span = startToken.span
		}

		return ast
	}
}

/***********
 * Imports *
 ***********/
var parseImport = astGenerator(function() {
	debug('parseImport')
	advance(['string','name'])
	if (gToken.type == 'string') {
		return { type: 'IMPORT_FILE', path: gToken.value }
	} else {
		return { type: 'IMPORT_MODULE', name: gToken.value }
	}
})

/*****************************
 * Items, aliases and values *
 *****************************/
function parseValueOrAlias() {
	debug('parseValueOrAlias')
	advance()
	switch(gToken.type) {
		case 'name':
		    return parseAliasOrInvocation()
		case 'string':
		case 'number':
			return getStaticValue()
		case 'symbol':
			return parseValueLiteral();
		case 'keyword':
			return parseKeyword();
		default:
			halt('Unexpected value or alias token: ' + gToken.type + ' ' + gToken.value)
	}
}

function parseValueLiteral() {
	switch(gToken.value) {
		case '<':
			return parseXML()
		case L_CURLY:
		case L_ARRAY:
			return parseJSON()
		case '@':
			return parseItem()
		default:
			halt('Unexpected symbol "'+gToken.value+'". Expected XML or JSON')
	}
}

var parseItem = astGenerator(function() {
	debug('parseItem')
	assert(gToken.type == 'symbol' && gToken.value == '@')
	advance(['name', 'number'])
	var itemID = gToken.value
	if (isAhead('symbol', '.')) {
		// TODO parse property
	}
	return { type:'ITEM', id: itemID }
})

function parseKeywordValue() {
	switch(gToken.value) {
		case 'import': return parseImport()
		case 'template': return parseTemplate()
		case 'handler': return parseHandler()
		default:
			halt('Unexpected keyword "'+gToken.value+'"')
	}
}

var parseAliasOrInvocation = astGenerator(function() {
	debug('parseAliasOrInvocation')
	var namespace = parseNamespace()
	if (isAhead('symbol', L_PAREN)) {
		advance('symbol', L_PAREN)
		var args = parseValueList(R_PAREN)
		advance('symbol', R_PAREN)
		return { type:'INVOCATION', namespace:namespace, args:args }
	} else {
		return { type:'ALIAS', namespace:namespace }
	}
})

var getStaticValue = astGenerator(function() {
	debug('getStaticValue')
	assert(gToken.type == 'string' || gToken.type == 'number')
	return { type:'STATIC_VALUE', valueType:gToken.type, value:gToken.value }
})

function parseNamespace() {
	var namespace = []
	while(true) {
		assert(gToken.type == 'name')
		namespace.push(gToken.value)
		if (!isAhead('symbol', '.')) { break }
		advance('symbol', '.')
		advance('name')
	}
	return namespace;
}

/*******
 * XML *
 *******/
var parseXML = astGenerator(function() {
	debug('parseXML')
	advance('name', null, 'XML tag')
	var tagName = gToken.value,
		attributes = parseXMLAttributes()

	advance('symbol', ['>', '/'], 'end of XML tag')
	if (gToken.value == '/') {
		advance('symbol', '>', 'self-closing XML tag')
		return { type:'XML', tag:tagName, attributes:attributes, content:[] }
	} else {
		var statements = []
		while(true) {
			if (isAhead('symbol', '<') && isAhead('symbol', '/', 2)) { break }
			advance()
			statements.push(parseStatement())
		}

		advance('symbol', '<')
		advance('symbol', '/')
		advance('name', tagName, 'matching XML tags')
		// allow for attributes on closing tag, e.g. <button>"Click"</button onClick=handler(){ ... }>
		attributes = attributes.concat(parseXMLAttributes())
		advance('symbol', '>')

		return { type:'XML', tag:tagName, attributes:attributes, block:statements }
	}
})
var parseXMLAttributes = function() {
	debug('parseXMLAttributes')

	var XMLAttributes = []
	while (isAhead('name')) {
		var assignment = parseAssignment('XML_attribute')
		XMLAttributes.push({ namespace:assignment[0], value:assignment[1] })
	}

	return XMLAttributes
}

function parseAssignment(acceptDotNotation, msg) {
	debug('parseAssignment')
	var namespace
	advance('name', null, msg)
	if (acceptDotNotation) { namespace = parseNamespace() }
	else { namespace = gToken.value }
	advance('symbol', '=', msg)
	var value = parseValueOrAlias()
	return [namespace, value]
}

/****************
 * Declarations *
 ****************/
var parseDeclaration = astGenerator(function() {
	debug('parseDeclaration')
	var assignment = parseAssignment('declaration')
	return { type:'DECLARATION', namespace:assignment[0], value:assignment[1] }
})

/********
 * JSON *
 ********/
function parseJSON() {
	if (gToken.value == L_CURLY) { return parseAliasLiteral() }
	else { return parseListLiteral() }
}
var parseAliasLiteral = astGenerator(function() {
	debug('parseObjectLiteral')
	assert(gToken.type == 'symbol' && gToken.value == L_CURLY)
	var content = []
	while (true) {
		if (isAhead('symbol', R_CURLY)) { break }
		var nameValuePair = {}
		advance(['name','string'])
		nameValuePair.name = gToken.value
		advance('symbol', ':')
		nameValuePair.value = parseValueOrAlias()
		content.push(nameValuePair)
		if (!isAhead('symbol', ',')) { break }
		advance('symbol',',')
	}
	advance('symbol', R_CURLY, 'right curly at the end of the JSON object')
	return { type:'NESTED_ALIAS', content:content }
})
var parseArrayLiteral = astGenerator(function() {
	debug('parseListLiteral')
	assert(gToken.type == 'symbol' && gToken.value == L_ARRAY)
	var content = parseValueList(R_ARRAY)
	advance('symbol', R_ARRAY, 'right bracket at the end of the JSON array')
	return { type:'LIST', content:content }
})
function parseValueList(breakSymbol) {
	var list = []
	while (true) {
		if (isAhead('symbol', breakSymbol)) { break }
		list.push(parseValueOrAlias())
		if (!isAhead('symbol', ',')) { break }
		advance('symbol', ',')
	}
	return list
}

/*************
* For loops *
*************/
var parseForLoop = astGenerator(function() {
	debug('parseForLoop')

	// parse "(item in Global.items)"
	advance('symbol', L_PAREN, 'beginning of for_loop\'s iterator statement')
	advance('name', null, 'for_loop\'s iterator')
	var iterator = gToken.value
	advance('keyword', 'in', 'for_loop\'s "in" keyword')
	advance('name', null, 'for_loop\'s iterable value')
	var iterable = gToken.value
	advance('symbol', R_PAREN, 'end of for_loop\'s iterator statement')

	// parse "{ ... for loop statements ... }"
	var block = parseBlock('for_loop')

	return { type:'FOR_LOOP', iterable:iterable, iterator:iterator, block:block }
})

/****************
 * If statement *
 ****************/
var parseIfStatement = astGenerator(function() {
	debug('parseIfStatement')

	advance('symbol', L_PAREN, 'beginning of the if statement\'s conditional')
	var condition = parseCondition()
	advance('symbol', R_PAREN, 'end of the if statement\'s conditional')

	var ifBlock = parseBlock('if statement')

	var elseBlock = null
	if (isAhead('keyword', 'else')) {
		advance('keyword', 'else')
		elseBlock = parseBlock('else statement')
	}

	return { type:'IF_STATEMENT', condition:condition, ifBlock:ifBlock, elseBlock:elseBlock }
})
function parseCondition() {
	debug('parseCondition')
	// TODO Parse compond statements, e.g. if (age < 30 && (income > 10e6 || looks=='awesome'))
	var type = gToken.type,
		value = gToken.value

	// Only strings, numbers, and aliases allowed
	advance(['string', 'number', 'name'])
	var left = parseStatement()

	var comparison, right
	if (isAhead('symbol', ['<','<=','>','>=','=='])) {
		advance('symbol')
		comparison = gToken.value
		advance(['string', 'number', 'name'])
		var right = parseStatement()
	}

	return { left:left, comparison:comparison, right:right }
}

/************************
 * Templates & Handlers *
 ************************/
var parseTemplate = astGenerator(function() {
	debug('parseTemplate')
	var callable = parseCallable('template')
	return { type:'TEMPLATE', args:callable[0], block:callable[1] }
})

var parseHandler = astGenerator(function() {
	debug('parseHandler')
	var callable = parseCallable('handler')
	return { type:'HANDLER', args:callable[0], block:callable[1] }
})

function parseCallable(msg) {
	advance('symbol', L_PAREN)
	var args = parseArgumentList()
	advance('symbol', R_PAREN)
	var block = parseBlock(msg)
	return [args, block]
}

function parseArgumentList() {
	debug('parseArgumentList')
	var args = []
	while (true) {
		if (isAhead('symbol', R_PAREN)) { break }
		advance('name')
		args.push(gToken.value)
		if (!isAhead('symbol', ',')) { break }
		advance('symbol', ',')
	}
	return args
}
<commit_msg>Pass through the arguments given to astGenerator functions<commit_after>var sys = require('sys'),
	util = require('./util'),
	q = util.q,
	debug = util.debug

exports.parse = util.intercept('ParseError', doParse)

var L_PAREN = '(',
	R_PAREN = ')',
	L_CURLY = '{',
	R_CURLY = '}',
	L_ARRAY = '[',
	R_ARRAY = ']'

var gToken, gIndex, gTokens, gState, gAST

var ParseError = function(file, msg) {
	this.name = 'ParseError';
	this.message = ['on line', gToken.line + ',', 'column', gToken.column, 'of', '"'+file+'":', msg].join(' ')
}
ParseError.prototype = Error.protoype

function doParse(tokens) {
	gTokens = tokens
	gIndex = -1
	gToken = null
	gAST = []

	while (true) {
		if (gIndex + 1 == gTokens.length) { break }
		advance()
		gAST.push(parseStatement())
	}

	return gAST
}

var parseStatement = function() {
	switch(gToken.type) {
		case 'string':
		case 'number':
			return getStaticValue()
		case 'symbol':
			switch(gToken.value) {
				case '<': return parseXML()
				case '=': halt('Unexpected symbol "=". Did you forget a "let" at the beginning of the line?')
				default: halt('Unexpected symbol "'+gToken.value+'"')
			}
		case 'name':
			return parseAliasOrInvocation()
		case 'keyword':
			switch (gToken.value) {
				case 'import': return parseImport()
				case 'let': return parseDeclaration()
				case 'for': return parseForLoop()
				case 'if': return parseIfStatement()
				default: halt('Unexpected keyword "'+gToken.value+'" at the beginning of a statement')
			}
		default:
			halt('Unknown parse statement token: ' + gToken.type)
	}
}

function parseBlock(statementType) {
	advance('symbol', L_CURLY, 'beginning of the '+statementType+'\'s block')
	var block = []
	while(!isAhead('symbol', R_CURLY)) {
		advance()
		block.push(parseStatement())
	}
	advance('symbol', R_CURLY, 'end of the '+statementType+' statement\'s block')
	return block
}

/*******************
 * Utility methods *
 *******************/
var assert = function(ok, msg) { if (!ok) halt(msg) }
var halt = function(msg) {
	sys.puts(util.grabLine(gToken.file, gToken.line, gToken.column, gToken.span));
	throw new ParseError(gToken.file, msg)
}
var advance = function(type, value, expressionType) {
	var nextToken = gTokens[++gIndex]
	if (!nextToken) { halt('Unexpected end of file') }
	function check(v1, v2) {
		assert(v1 == v2,
			['Expected a', q(type),
				value ? 'of value ' + q(value) : '',
				expressionType ? 'for the ' + expressionType : '',
				'but found a', q(nextToken.type),
				'of value', q(nextToken.value)].join(' ')
	)}
	if (type) { check(findInArray(type, nextToken.type), nextToken.type) }
	if (value) { check(findInArray(value, nextToken.value), nextToken.value) }
	gToken = nextToken
}
var isAhead = function(type, value, steps) {
	var token = gTokens[gIndex + (steps || 1)]
	if (!token) { return false }
	if (type && findInArray(type, token.type) != token.type) { return false }
	if (value && findInArray(value, token.value) != token.value) { return false }
	return true
}
// Find an item in an array and return it
//  if target is in array, return target
//  if target is not in array, return array
//  if array is not an array, return array
var findInArray = function(array, target) {
	if (!(array instanceof Array)) { return array }
	for (var i=0, item; item = array[i]; i++) {
		if (item == target) { return item }
	}
	return array
}

function astGenerator(generatorFn) {
	return function() {
		var startToken = gToken,
			ast = generatorFn.apply(this, arguments),
			endToken = gToken

		ast.file = startToken.file
		ast.line = startToken.line
		ast.column = startToken.column
		ast.lineEnd = endToken.line
		ast.columnEnd = endToken.column
		if (ast.line == ast.lineEnd) {
			ast.span = endToken.column - startToken.column + endToken.span
		} else {
			ast.span = startToken.span
		}

		return ast
	}
}

/***********
 * Imports *
 ***********/
var parseImport = astGenerator(function() {
	debug('parseImport')
	advance(['string','name'])
	if (gToken.type == 'string') {
		return { type: 'IMPORT_FILE', path: gToken.value }
	} else {
		return { type: 'IMPORT_MODULE', name: gToken.value }
	}
})

/*****************************
 * Items, aliases and values *
 *****************************/
function parseValueOrAlias() {
	debug('parseValueOrAlias')
	advance()
	switch(gToken.type) {
		case 'name':
		    return parseAliasOrInvocation()
		case 'string':
		case 'number':
			return getStaticValue()
		case 'symbol':
			return parseValueLiteral();
		case 'keyword':
			return parseKeyword();
		default:
			halt('Unexpected value or alias token: ' + gToken.type + ' ' + gToken.value)
	}
}

function parseValueLiteral() {
	switch(gToken.value) {
		case '<':
			return parseXML()
		case L_CURLY:
		case L_ARRAY:
			return parseJSON()
		case '@':
			return parseItem()
		default:
			halt('Unexpected symbol "'+gToken.value+'". Expected XML or JSON')
	}
}

var parseItem = astGenerator(function() {
	debug('parseItem')
	assert(gToken.type == 'symbol' && gToken.value == '@')
	advance(['name', 'number'])
	var itemID = gToken.value
	if (isAhead('symbol', '.')) {
		// TODO parse property
	}
	return { type:'ITEM', id: itemID }
})

function parseKeywordValue() {
	switch(gToken.value) {
		case 'import': return parseImport()
		case 'template': return parseTemplate()
		case 'handler': return parseHandler()
		default:
			halt('Unexpected keyword "'+gToken.value+'"')
	}
}

var parseAliasOrInvocation = astGenerator(function() {
	debug('parseAliasOrInvocation')
	var namespace = parseNamespace()
	if (isAhead('symbol', L_PAREN)) {
		advance('symbol', L_PAREN)
		var args = parseValueList(R_PAREN)
		advance('symbol', R_PAREN)
		return { type:'INVOCATION', namespace:namespace, args:args }
	} else {
		return { type:'ALIAS', namespace:namespace }
	}
})

var getStaticValue = astGenerator(function() {
	debug('getStaticValue')
	assert(gToken.type == 'string' || gToken.type == 'number')
	return { type:'STATIC_VALUE', valueType:gToken.type, value:gToken.value }
})

function parseNamespace() {
	var namespace = []
	while(true) {
		assert(gToken.type == 'name')
		namespace.push(gToken.value)
		if (!isAhead('symbol', '.')) { break }
		advance('symbol', '.')
		advance('name')
	}
	return namespace;
}

/*******
 * XML *
 *******/
var parseXML = astGenerator(function() {
	debug('parseXML')
	advance('name', null, 'XML tag')
	var tagName = gToken.value,
		attributes = parseXMLAttributes()

	advance('symbol', ['>', '/'], 'end of XML tag')
	if (gToken.value == '/') {
		advance('symbol', '>', 'self-closing XML tag')
		return { type:'XML', tag:tagName, attributes:attributes, content:[] }
	} else {
		var statements = []
		while(true) {
			if (isAhead('symbol', '<') && isAhead('symbol', '/', 2)) { break }
			advance()
			statements.push(parseStatement())
		}

		advance('symbol', '<')
		advance('symbol', '/')
		advance('name', tagName, 'matching XML tags')
		// allow for attributes on closing tag, e.g. <button>"Click"</button onClick=handler(){ ... }>
		attributes = attributes.concat(parseXMLAttributes())
		advance('symbol', '>')

		return { type:'XML', tag:tagName, attributes:attributes, block:statements }
	}
})
var parseXMLAttributes = function() {
	debug('parseXMLAttributes')

	var XMLAttributes = []
	while (isAhead('name')) {
		var assignment = parseAssignment('XML_attribute')
		XMLAttributes.push({ namespace:assignment[0], value:assignment[1] })
	}

	return XMLAttributes
}

function parseAssignment(acceptDotNotation, msg) {
	debug('parseAssignment')
	var namespace
	advance('name', null, msg)
	if (acceptDotNotation) { namespace = parseNamespace() }
	else { namespace = gToken.value }
	advance('symbol', '=', msg)
	var value = parseValueOrAlias()
	return [namespace, value]
}

/****************
 * Declarations *
 ****************/
var parseDeclaration = astGenerator(function() {
	debug('parseDeclaration')
	var assignment = parseAssignment('declaration')
	return { type:'DECLARATION', namespace:assignment[0], value:assignment[1] }
})

/********
 * JSON *
 ********/
function parseJSON() {
	if (gToken.value == L_CURLY) { return parseAliasLiteral() }
	else { return parseListLiteral() }
}
var parseAliasLiteral = astGenerator(function() {
	debug('parseObjectLiteral')
	assert(gToken.type == 'symbol' && gToken.value == L_CURLY)
	var content = []
	while (true) {
		if (isAhead('symbol', R_CURLY)) { break }
		var nameValuePair = {}
		advance(['name','string'])
		nameValuePair.name = gToken.value
		advance('symbol', ':')
		nameValuePair.value = parseValueOrAlias()
		content.push(nameValuePair)
		if (!isAhead('symbol', ',')) { break }
		advance('symbol',',')
	}
	advance('symbol', R_CURLY, 'right curly at the end of the JSON object')
	return { type:'NESTED_ALIAS', content:content }
})
var parseArrayLiteral = astGenerator(function() {
	debug('parseListLiteral')
	assert(gToken.type == 'symbol' && gToken.value == L_ARRAY)
	var content = parseValueList(R_ARRAY)
	advance('symbol', R_ARRAY, 'right bracket at the end of the JSON array')
	return { type:'LIST', content:content }
})
function parseValueList(breakSymbol) {
	var list = []
	while (true) {
		if (isAhead('symbol', breakSymbol)) { break }
		list.push(parseValueOrAlias())
		if (!isAhead('symbol', ',')) { break }
		advance('symbol', ',')
	}
	return list
}

/*************
* For loops *
*************/
var parseForLoop = astGenerator(function() {
	debug('parseForLoop')

	// parse "(item in Global.items)"
	advance('symbol', L_PAREN, 'beginning of for_loop\'s iterator statement')
	advance('name', null, 'for_loop\'s iterator')
	var iterator = gToken.value
	advance('keyword', 'in', 'for_loop\'s "in" keyword')
	advance('name', null, 'for_loop\'s iterable value')
	var iterable = gToken.value
	advance('symbol', R_PAREN, 'end of for_loop\'s iterator statement')

	// parse "{ ... for loop statements ... }"
	var block = parseBlock('for_loop')

	return { type:'FOR_LOOP', iterable:iterable, iterator:iterator, block:block }
})

/****************
 * If statement *
 ****************/
var parseIfStatement = astGenerator(function() {
	debug('parseIfStatement')

	advance('symbol', L_PAREN, 'beginning of the if statement\'s conditional')
	var condition = parseCondition()
	advance('symbol', R_PAREN, 'end of the if statement\'s conditional')

	var ifBlock = parseBlock('if statement')

	var elseBlock = null
	if (isAhead('keyword', 'else')) {
		advance('keyword', 'else')
		elseBlock = parseBlock('else statement')
	}

	return { type:'IF_STATEMENT', condition:condition, ifBlock:ifBlock, elseBlock:elseBlock }
})
function parseCondition() {
	debug('parseCondition')
	// TODO Parse compond statements, e.g. if (age < 30 && (income > 10e6 || looks=='awesome'))
	var type = gToken.type,
		value = gToken.value

	// Only strings, numbers, and aliases allowed
	advance(['string', 'number', 'name'])
	var left = parseStatement()

	var comparison, right
	if (isAhead('symbol', ['<','<=','>','>=','=='])) {
		advance('symbol')
		comparison = gToken.value
		advance(['string', 'number', 'name'])
		var right = parseStatement()
	}

	return { left:left, comparison:comparison, right:right }
}

/************************
 * Templates & Handlers *
 ************************/
var parseTemplate = astGenerator(function() {
	debug('parseTemplate')
	var callable = parseCallable('template')
	return { type:'TEMPLATE', args:callable[0], block:callable[1] }
})

var parseHandler = astGenerator(function() {
	debug('parseHandler')
	var callable = parseCallable('handler')
	return { type:'HANDLER', args:callable[0], block:callable[1] }
})

function parseCallable(msg) {
	advance('symbol', L_PAREN)
	var args = parseArgumentList()
	advance('symbol', R_PAREN)
	var block = parseBlock(msg)
	return [args, block]
}

function parseArgumentList() {
	debug('parseArgumentList')
	var args = []
	while (true) {
		if (isAhead('symbol', R_PAREN)) { break }
		advance('name')
		args.push(gToken.value)
		if (!isAhead('symbol', ',')) { break }
		advance('symbol', ',')
	}
	return args
}
<|endoftext|>


<commit_before>/*---------------------------------------------------------
 * OpenERP web_graph
 *---------------------------------------------------------*/

/* jshint undef: false  */

openerp.web_graph = function (instance) {
'use strict';

var _lt = instance.web._lt;
var _t = instance.web._t;
var QWeb = instance.web.qweb;

instance.web.views.add('graph', 'instance.web_graph.GraphView');

 /**
  * GraphView view.  It mostly contains a widget (PivotTable), some data, and
  * calls to charts function.
  */
instance.web_graph.GraphView = instance.web.View.extend({
    template: 'GraphView',
    display_name: _lt('Graph'),
    view_type: 'graph',

    events: {
        'click .graph_mode_selection li' : function (event) {
            event.preventDefault();
            var mode = event.target.attributes['data-mode'].nodeValue;
            this.switch_mode(mode);
        },

        'click .web_graph_click' : function (event) {
            event.preventDefault();
            var id = event.target.attributes['data-id'].nodeValue;
            this.handle_header_event({id:id, event:event});
        },

        'click a.field-selection' : function (event) {
            var id = event.target.attributes['data-id'].nodeValue,
                field_id = event.target.attributes['data-field-id'].nodeValue;
            event.preventDefault();
            this.dropdown.remove();
            this.pivot_table.expand(id, field_id)
                .then(this.proxy('draw_table'));
        },

        'click label.graph_swap_axis' : function (event) {
            this.pivot_table.swap_axis();
            this.draw_table();
        },

        'click label.graph_fold_all' : function (event) {
            this.pivot_table.fold_all();
            this.draw_table();
        },

        'click label.graph_fold_rows' : function (event) {
            this.pivot_table.fold_rows();
            this.draw_table();
        },

        'click label.graph_fold_cols' : function (event) {
            this.pivot_table.fold_cols();
            this.draw_table();
        },

        'click label.graph_heat_map' : function (event) {
            this.heat_map_mode = !this.heat_map_mode;
            this.draw_table();
        },
    },

    view_loading: function (fields_view_get) {
        var self = this,
            model = new instance.web.Model(fields_view_get.model, {group_by_no_leaf: true}),
            domain = [],
            measure = null,
            fields,
            important_fields = [],
            col_groupby = [],
            row_groupby = [];

        this.pivot_table = null;
        this.heat_map_mode = false;

        // get the default groupbys and measure defined in the field view
        _.each(fields_view_get.arch.children, function (field) {
            if ('name' in field.attrs) {
                if ('operator' in field.attrs) {
                    measure = field.attrs.name;
                } else {
                    row_groupby.push(field.attrs.name);
                }
            }
        });

        // get the most important fields (of the model) by looking at the
        // groupby filters defined in the search view
        var load_view = instance.web.fields_view_get({
            model: model,
            view_type: 'search',
        });

        var important_fields_def = $.when(load_view).then(function (search_view) {
            var groups = _.select(search_view.arch.children, function (c) {
                return (c.tag == 'group') && (c.attrs.string != 'Display');
            });
            _.each(groups, function(g) {
                _.each(g.children, function (g) {
                    if (g.attrs.context) {
                        var field_id = py.eval(g.attrs.context).group_by;
                        important_fields.push(field_id);
                    }
                });
            });
        });

        // get the fields descriptions from the model
        var field_descr_def = model.call('fields_get', [])
            .then(function (fs) { fields = fs; });

        return $.when(important_fields_def, field_descr_def)
            .then(function () {
                self.data = {
                    model: model,
                    domain: domain,
                    fields: fields,
                    important_fields: important_fields,
                    measure: measure,
                    measure_label: fields[measure].string,
                    col_groupby: [],
                    row_groupby: row_groupby,
                    groups: [],
                    total: null,
                };
            });
    },

    start: function () {
        this.table = $('

');
        this.svg = $('
<svg></svg>
');
        this.$el.filter('.graph_main_content').append(this.table);
        this.$el.filter('.graph_main_content').append(this.svg);
        return this.load_view();
    },

    do_search: function (domain, context, group_by) {
        var self = this;
        this.data.domain = new instance.web.CompoundDomain(domain);

        if (!this.pivot_table) {
            self.pivot_table = new PivotTable(self.data);
            self.pivot_table.start().then(self.proxy('draw_table'));
        } else {
            this.pivot_table.update_values.done(function () {
                self.draw_table();
            });
        }
    },

    do_show: function () {
        this.do_push_state({});
        return this._super();
    },

    switch_mode: function (mode) {
        this.mode = mode;
        if (mode === 'pivot') {
            this.table.css('display', 'block');
            this.svg.css('display','none');
        } else {
            this.table.css('display', 'none');
            this.svg.remove();
            this.svg = $('
<svg></svg>
');
            this.$el.filter('.graph_main_content').append(this.svg);
            draw_chart(mode, this.pivot_table);

        }
    },

    handle_header_event: function (options) {
        var pivot = this.pivot_table,
            id = options.id,
            header = pivot.get_header(id);

        if (header.is_expanded) {
            pivot.fold(header);
            this.draw_table();
        } else {
            if (header.path.length < header.root.groupby.length) {
                var field = header.root.groupby[header.path.length];
                pivot.expand(id, field).then(this.proxy('draw_table'));
            } else {
                this.display_dropdown({id:header.id,
                                       target: $(options.event.target),
                                       x: options.event.pageX,
                                       y: options.event.pageY});
            }
        }
    },

    display_dropdown: function (options) {
        var self = this,
            pivot = this.pivot_table,
            already_grouped = pivot.rows.groupby.concat(pivot.cols.groupby),
            possible_groups = _.difference(self.data.important_fields, already_grouped),
            dropdown_options = {
                header_id: options.id,
                fields: _.map(possible_groups, function (field) {
                    return {id: field, value: self.data.fields[field].string};
            })};

        this.dropdown = $(QWeb.render('field_selection', dropdown_options));
        options.target.after(this.dropdown);
        this.dropdown.css({position:'absolute',
                           left:options.x,
                           top:options.y});
        $('.field-selection').next('.dropdown-menu').toggle();
    },

    draw_table: function () {
        this.table.empty();
        this.draw_top_headers();
        _.each(this.pivot_table.rows.headers, this.proxy('draw_row'));
    },

    make_border_cell: function (colspan, rowspan) {
        return $('	').addClass('graph_border')
                             .attr('colspan', colspan)
                             .attr('rowspan', rowspan);
    },

    make_header_title: function (header) {
        return $(' ')
            .addClass('web_graph_click')
            .attr('href', '#')
            .addClass((header.is_expanded) ? 'icon-minus-sign' : 'icon-plus-sign')
            .append(header.name);
    },

    draw_top_headers: function () {
        var self = this,
            pivot = this.pivot_table,
            height = _.max(_.map(pivot.cols.headers, function(g) {return g.path.length;})),
            header_cells = [[this.make_border_cell(1, height)]];

        function set_dim (cols) {
            _.each(cols.children, set_dim);
            if (cols.children.length === 0) {
                cols.height = height - cols.path.length + 1;
                cols.width = 1;
            } else {
                cols.height = 1;
                cols.width = _.reduce(cols.children, function (sum,c) { return sum + c.width;}, 0);
            }
        }

        function make_col_header (col) {
            var cell = self.make_border_cell(col.width, col.height);
            return cell.append(self.make_header_title(col).attr('data-id', col.id));
        }

        function make_cells (queue, level) {
            var col = queue[0];
            queue = _.rest(queue).concat(col.children);
            if (col.path.length == level) {
                _.last(header_cells).push(make_col_header(col));
            } else {
                level +=1;
                header_cells.push([make_col_header(col)]);
            }
            if (queue.length !== 0) {
                make_cells(queue, level);
            }
        }

        set_dim(pivot.cols.main);  // add width and height info to columns headers
        if (pivot.cols.main.children.length === 0) {
            make_cells(pivot.cols.headers, 0);
        } else {
            make_cells(pivot.cols.main.children, 1);
        }

        _.each(header_cells, function (cells) {
            self.table.append($("

").append(cells));
        });
    },

    draw_row: function (row) {
        var self = this,
            pivot = this.pivot_table,
            html_row = $('

'),
            row_header = this.make_border_cell(1,1)
                .append(this.make_header_title(row).attr('data-id', row.id))
                .addClass('graph_border');

        for (var i in _.range(row.path.length)) {
            row_header.prepend($('', {class:'web_graph_indent'}));
        }

        html_row.append(row_header);

        _.each(pivot.cols.headers, function (col) {
            if (col.children.length === 0) {
                var value = pivot.get_value(row.id, col.id),
                    cell = $('	');

                cell.append((value === undefined) ? '' : value);
                if ((self.heat_map_mode) && (value !== undefined)) {
                    var color = Math.floor(50 + 205*(pivot.total - value)/pivot.total);
                    cell.css("background-color", "rgb(255," + color + "," + color + ")");
                }
                html_row.append(cell);
            }
        });
        this.table.append(html_row);
    }
});

};
<commit_msg>[IMP] adds slight color to expanded rows to make it easier to see the subrows (addon web_graph)<commit_after>/*---------------------------------------------------------
 * OpenERP web_graph
 *---------------------------------------------------------*/

/* jshint undef: false  */

openerp.web_graph = function (instance) {
'use strict';

var _lt = instance.web._lt;
var _t = instance.web._t;
var QWeb = instance.web.qweb;

instance.web.views.add('graph', 'instance.web_graph.GraphView');

 /**
  * GraphView view.  It mostly contains a widget (PivotTable), some data, and
  * calls to charts function.
  */
instance.web_graph.GraphView = instance.web.View.extend({
    template: 'GraphView',
    display_name: _lt('Graph'),
    view_type: 'graph',

    events: {
        'click .graph_mode_selection li' : function (event) {
            event.preventDefault();
            var mode = event.target.attributes['data-mode'].nodeValue;
            this.switch_mode(mode);
        },

        'click .web_graph_click' : function (event) {
            event.preventDefault();
            var id = event.target.attributes['data-id'].nodeValue;
            this.handle_header_event({id:id, event:event});
        },

        'click a.field-selection' : function (event) {
            var id = event.target.attributes['data-id'].nodeValue,
                field_id = event.target.attributes['data-field-id'].nodeValue;
            event.preventDefault();
            this.dropdown.remove();
            this.pivot_table.expand(id, field_id)
                .then(this.proxy('draw_table'));
        },

        'click label.graph_swap_axis' : function (event) {
            this.pivot_table.swap_axis();
            this.draw_table();
        },

        'click label.graph_fold_all' : function (event) {
            this.pivot_table.fold_all();
            this.draw_table();
        },

        'click label.graph_fold_rows' : function (event) {
            this.pivot_table.fold_rows();
            this.draw_table();
        },

        'click label.graph_fold_cols' : function (event) {
            this.pivot_table.fold_cols();
            this.draw_table();
        },

        'click label.graph_heat_map' : function (event) {
            this.heat_map_mode = !this.heat_map_mode;
            this.draw_table();
        },
    },

    view_loading: function (fields_view_get) {
        var self = this,
            model = new instance.web.Model(fields_view_get.model, {group_by_no_leaf: true}),
            domain = [],
            measure = null,
            fields,
            important_fields = [],
            col_groupby = [],
            row_groupby = [];

        this.pivot_table = null;
        this.heat_map_mode = false;

        // get the default groupbys and measure defined in the field view
        _.each(fields_view_get.arch.children, function (field) {
            if ('name' in field.attrs) {
                if ('operator' in field.attrs) {
                    measure = field.attrs.name;
                } else {
                    row_groupby.push(field.attrs.name);
                }
            }
        });

        // get the most important fields (of the model) by looking at the
        // groupby filters defined in the search view
        var load_view = instance.web.fields_view_get({
            model: model,
            view_type: 'search',
        });

        var important_fields_def = $.when(load_view).then(function (search_view) {
            var groups = _.select(search_view.arch.children, function (c) {
                return (c.tag == 'group') && (c.attrs.string != 'Display');
            });
            _.each(groups, function(g) {
                _.each(g.children, function (g) {
                    if (g.attrs.context) {
                        var field_id = py.eval(g.attrs.context).group_by;
                        important_fields.push(field_id);
                    }
                });
            });
        });

        // get the fields descriptions from the model
        var field_descr_def = model.call('fields_get', [])
            .then(function (fs) { fields = fs; });

        return $.when(important_fields_def, field_descr_def)
            .then(function () {
                self.data = {
                    model: model,
                    domain: domain,
                    fields: fields,
                    important_fields: important_fields,
                    measure: measure,
                    measure_label: fields[measure].string,
                    col_groupby: [],
                    row_groupby: row_groupby,
                    groups: [],
                    total: null,
                };
            });
    },

    start: function () {
        this.table = $('

');
        this.svg = $('
<svg></svg>
');
        this.$el.filter('.graph_main_content').append(this.table);
        this.$el.filter('.graph_main_content').append(this.svg);
        return this.load_view();
    },

    do_search: function (domain, context, group_by) {
        var self = this;
        this.data.domain = new instance.web.CompoundDomain(domain);

        if (!this.pivot_table) {
            self.pivot_table = new PivotTable(self.data);
            self.pivot_table.start().then(self.proxy('draw_table'));
        } else {
            this.pivot_table.update_values.done(function () {
                self.draw_table();
            });
        }
    },

    do_show: function () {
        this.do_push_state({});
        return this._super();
    },

    switch_mode: function (mode) {
        this.mode = mode;
        if (mode === 'pivot') {
            this.table.css('display', 'block');
            this.svg.css('display','none');
        } else {
            this.table.css('display', 'none');
            this.svg.remove();
            this.svg = $('
<svg></svg>
');
            this.$el.filter('.graph_main_content').append(this.svg);
            draw_chart(mode, this.pivot_table);

        }
    },

    handle_header_event: function (options) {
        var pivot = this.pivot_table,
            id = options.id,
            header = pivot.get_header(id);

        if (header.is_expanded) {
            pivot.fold(header);
            this.draw_table();
        } else {
            if (header.path.length < header.root.groupby.length) {
                var field = header.root.groupby[header.path.length];
                pivot.expand(id, field).then(this.proxy('draw_table'));
            } else {
                this.display_dropdown({id:header.id,
                                       target: $(options.event.target),
                                       x: options.event.pageX,
                                       y: options.event.pageY});
            }
        }
    },

    display_dropdown: function (options) {
        var self = this,
            pivot = this.pivot_table,
            already_grouped = pivot.rows.groupby.concat(pivot.cols.groupby),
            possible_groups = _.difference(self.data.important_fields, already_grouped),
            dropdown_options = {
                header_id: options.id,
                fields: _.map(possible_groups, function (field) {
                    return {id: field, value: self.data.fields[field].string};
            })};

        this.dropdown = $(QWeb.render('field_selection', dropdown_options));
        options.target.after(this.dropdown);
        this.dropdown.css({position:'absolute',
                           left:options.x,
                           top:options.y});
        $('.field-selection').next('.dropdown-menu').toggle();
    },

    draw_table: function () {
        this.table.empty();
        this.draw_top_headers();
        _.each(this.pivot_table.rows.headers, this.proxy('draw_row'));
    },

    make_border_cell: function (colspan, rowspan) {
        return $('	').addClass('graph_border')
                             .attr('colspan', colspan)
                             .attr('rowspan', rowspan);
    },

    make_header_title: function (header) {
        return $(' ')
            .addClass('web_graph_click')
            .attr('href', '#')
            .addClass((header.is_expanded) ? 'icon-minus-sign' : 'icon-plus-sign')
            .append(header.name);
    },

    draw_top_headers: function () {
        var self = this,
            pivot = this.pivot_table,
            height = _.max(_.map(pivot.cols.headers, function(g) {return g.path.length;})),
            header_cells = [[this.make_border_cell(1, height)]];

        function set_dim (cols) {
            _.each(cols.children, set_dim);
            if (cols.children.length === 0) {
                cols.height = height - cols.path.length + 1;
                cols.width = 1;
            } else {
                cols.height = 1;
                cols.width = _.reduce(cols.children, function (sum,c) { return sum + c.width;}, 0);
            }
        }

        function make_col_header (col) {
            var cell = self.make_border_cell(col.width, col.height);
            return cell.append(self.make_header_title(col).attr('data-id', col.id));
        }

        function make_cells (queue, level) {
            var col = queue[0];
            queue = _.rest(queue).concat(col.children);
            if (col.path.length == level) {
                _.last(header_cells).push(make_col_header(col));
            } else {
                level +=1;
                header_cells.push([make_col_header(col)]);
            }
            if (queue.length !== 0) {
                make_cells(queue, level);
            }
        }

        set_dim(pivot.cols.main);  // add width and height info to columns headers
        if (pivot.cols.main.children.length === 0) {
            make_cells(pivot.cols.headers, 0);
        } else {
            make_cells(pivot.cols.main.children, 1);
        }

        _.each(header_cells, function (cells) {
            self.table.append($("

").append(cells));
        });
    },

    draw_row: function (row) {
        var self = this,
            pivot = this.pivot_table,
            html_row = $('

'),
            row_header = this.make_border_cell(1,1)
                .append(this.make_header_title(row).attr('data-id', row.id))
                .addClass('graph_border');

        for (var i in _.range(row.path.length)) {
            row_header.prepend($('', {class:'web_graph_indent'}));
        }

        html_row.append(row_header);

        _.each(pivot.cols.headers, function (col) {
            if (col.children.length === 0) {
                var value = pivot.get_value(row.id, col.id),
                    cell = $('	');

                cell.append((value === undefined) ? '' : value);
                if ((self.heat_map_mode) && (value !== undefined)) {
                    var color = Math.floor(50 + 205*(pivot.total - value)/pivot.total);
                    cell.css("background-color", "rgb(255," + color + "," + color + ")");
                }
                if (row.is_expanded && (row.path.length <= 2)) {
                    var color = row.path.length *   5 + 240;
                    cell.css("background-color", "rgb(" + [color, color, color].join() + ")");
                }
                html_row.append(cell);
            }
        });
        this.table.append(html_row);
    }
});

};
<|endoftext|>


<commit_before>dojo.provide("dijit._Container");

dojo.declare("dijit._Contained",
	null,
	{
		// summary
		//		Mixin for widgets that are children of a container widget
		//
		// example:
		// | 	// make a basic custom widget that knows about it's parents
		// |	dojo.declare("my.customClass",[dijit._Widget,dijit._Contained],{});
		//
		getParent: function(){
			// summary:
			//		Returns the parent widget of this widget, assuming the parent
			//		implements dijit._Container
			for(var p=this.domNode.parentNode; p; p=p.parentNode){
				var id = p.getAttribute && p.getAttribute("widgetId");
				if(id){
					var parent = dijit.byId(id);
					return parent.isContainer ? parent : null;
				}
			}
			return null;
		},

		_getSibling: function(which){
			var node = this.domNode;
			do{
				node = node[which+"Sibling"];
			}while(node && node.nodeType != 1);
			if(!node){ return null; } // null
			var id = node.getAttribute("widgetId");
			return dijit.byId(id);
		},

		getPreviousSibling: function(){
			// summary:
			//		Returns null if this is the first child of the parent,
			//		otherwise returns the next element sibling to the "left".

			return this._getSibling("previous"); // Mixed
		},

		getNextSibling: function(){
			// summary:
			//		Returns null if this is the last child of the parent,
			//		otherwise returns the next element sibling to the "right".

			return this._getSibling("next"); // Mixed
		},

		getIndexInParent: function(){
			// summary:
			//		Returns the index of this widget within its container parent.
			//		It returns -1 if the parent does not exist, or if the parent
			//		is not a dijit._Container

			var p = this.getParent();
			if(!p || !p.getIndexOfChild){
				return -1; // int
			}
			return p.getIndexOfChild(this); // int
		}
	}
);

dojo.declare("dijit._Container",
	null,
	{
		// summary:
		//		Mixin for widgets that contain a set of widget children.
		// description:
		//		Use this mixin for widgets that needs to know about and
		//		keep track of their widget children. Suitable for widgets like BorderContainer
		//		and TabContainer which contain (only) a set of child widgets.
		//
		//		It's not suitable for widgets like ContentPane
		//		which contains mixed HTML (plain DOM nodes in addition to widgets),
		//		and where contained widgets are not necessarily directly below
		//		this.containerNode.   In that case calls like addChild(node, position)
		//		wouldn't make sense.

		// isContainer: Boolean
		//		Just a flag indicating that this widget descends from dijit._Container
		isContainer: true,

		buildRendering: function(){
			this.inherited(arguments);
			if(!this.containerNode){
				// all widgets with descendants must set containerNode
   				this.containerNode = this.domNode;
			}
		},

		addChild: function(/*Widget*/ widget, /*int?*/ insertIndex){
			// summary:
			//		Makes the given widget a child of this widget.
			// description:
			//		Inserts specified child widget's dom node as a child of this widget's
			//		container node, and possibly does otherprocessing (such as layout).

			var refNode = this.containerNode;
			if(insertIndex && typeof insertIndex == "number"){
				var children = dojo.query("> [widgetId]", refNode);
				if(children && children.length >= insertIndex){
					refNode = children[insertIndex-1];
					insertIndex = "after";
				}
			}
			dojo.place(widget.domNode, refNode, insertIndex);

			// If I've been started but the child widget hasn't been started,
			// start it now.  Make sure to do this after widget has been
			// inserted into the DOM tree, so it can see that it's being controlled by me,
			// so it doesn't try to size itself.
			if(this._started && !widget._started){
				widget.startup();
			}
		},

		removeChild: function(/*Widget or int*/ widget){
			// summary:
			//		Removes the passed widget instance from this widget but does
			//		not destroy it.  You can also pass in an integer indicating
			//		the index within the container to remove
			if(typeof widget == "number" && widget > 0){
				widget = this.getChildren()[widget];
			}
			// If we cannot find the widget, just return
			if(!widget || !widget.domNode){ return; }

			var node = widget.domNode;
			node.parentNode.removeChild(node);	// detach but don't destroy
		},

		_nextElement: function(node){
			do{
				node = node.nextSibling;
			}while(node && node.nodeType != 1);
			return node;
		},

		_firstElement: function(node){
			node = node.firstChild;
			if(node && node.nodeType != 1){
				node = this._nextElement(node);
			}
			return node;
		},

		getChildren: function(){
			// summary:
			//		Returns array of children widgets.
			// description:
			//		Returns the widgets that are directly under this.containerNode.
			return dojo.query("> [widgetId]", this.containerNode).map(dijit.byNode); // Widget[]
		},

		hasChildren: function(){
			// summary:
			//		Returns true if widget has children, i.e. if this.containerNode contains something.
			return !!this._firstElement(this.containerNode); // Boolean
		},

		destroyDescendants: function(/*Boolean*/ preserveDom){
			dojo.forEach(this.getChildren(), function(child){ child.destroyRecursive(preserveDom); });
		},

		_getSiblingOfChild: function(/*Widget*/ child, /*int*/ dir){
			// summary:
			//		Get the next or previous widget sibling of child
			// dir:
			//		if 1, get the next sibling
			//		if -1, get the previous sibling
			var node = child.domNode;
			var which = (dir>0 ? "nextSibling" : "previousSibling");
			do{
				node = node[which];
			}while(node && (node.nodeType != 1 || !dijit.byNode(node)));
			return node ? dijit.byNode(node) : null;
		},

		getIndexOfChild: function(/*Widget*/ child){
			// summary:
			//		Gets the index of the child in this container or -1 if not found
			var children = this.getChildren();
			for(var i=0, c; c=children[i]; i++){
				if(c == child){
					return i; // int
				}
			}
			return -1; // int
		}
	}
);

dojo.declare("dijit._KeyNavContainer",
	[dijit._Container],
	{

		// summary: A _Container with keyboard navigation of its children.
		// decscription:
		//		To use this mixin, call connectKeyNavHandlers() in
		//		postCreate() and call startupKeyNavChildren() in startup().
		//		It provides normalized keyboard and focusing code for Container
		//		widgets.
/*=====
		// focusedChild: Widget
		//		The currently focused child widget, or null if there isn't one
		focusedChild: null,
=====*/

		_keyNavCodes: {},

		connectKeyNavHandlers: function(/*Array*/ prevKeyCodes, /*Array*/ nextKeyCodes){
			// summary:
			//		Call in postCreate() to attach the keyboard handlers
			//		to the container.
			// preKeyCodes: Array
			//		Key codes for navigating to the previous child.
			// nextKeyCodes: Array
			//		Key codes for navigating to the next child.

			var keyCodes = this._keyNavCodes = {};
			var prev = dojo.hitch(this, this.focusPrev);
			var next = dojo.hitch(this, this.focusNext);
			dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev });
			dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next });
			this.connect(this.domNode, "onkeypress", "_onContainerKeypress");
			this.connect(this.domNode, "onfocus", "_onContainerFocus");
		},

		startupKeyNavChildren: function(){
			// summary:
			//		Call in startup() to set child tabindexes to -1
			dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild"));
		},

		addChild: function(/*Widget*/ widget, /*int?*/ insertIndex){
			// summary: Add a child to our _Container
			dijit._KeyNavContainer.superclass.addChild.apply(this, arguments);
			this._startupChild(widget);
		},

		focus: function(){
			// summary: Default focus() implementation: focus the first child.
			this.focusFirstChild();
		},

		focusFirstChild: function(){
			// summary: Focus the first focusable child in the container.
			this.focusChild(this._getFirstFocusableChild());
		},

		focusNext: function(){
			// summary: Focus the next widget or focal node (for widgets
			//		with multiple focal nodes) within this container.
			if(this.focusedChild && this.focusedChild.hasNextFocalNode
					&& this.focusedChild.hasNextFocalNode()){
				this.focusedChild.focusNext();
				return;
			}
			var child = this._getNextFocusableChild(this.focusedChild, 1);
			if(child.getFocalNodes){
				this.focusChild(child, child.getFocalNodes()[0]);
			}else{
				this.focusChild(child);
			}
		},

		focusPrev: function(){
			// summary: Focus the previous widget or focal node (for widgets
			//		with multiple focal nodes) within this container.
			if(this.focusedChild && this.focusedChild.hasPrevFocalNode
					&& this.focusedChild.hasPrevFocalNode()){
				this.focusedChild.focusPrev();
				return;
			}
			var child = this._getNextFocusableChild(this.focusedChild, -1);
			if(child.getFocalNodes){
				var nodes = child.getFocalNodes();
				this.focusChild(child, nodes[nodes.length-1]);
			}else{
				this.focusChild(child);
			}
		},

		focusChild: function(/*Widget*/ widget, /*Node?*/ node){
			// summary: Focus widget. Optionally focus 'node' within widget.
			if(widget){
				if(this.focusedChild && widget !== this.focusedChild){
					this._onChildBlur(this.focusedChild);
				}
				this.focusedChild = widget;
				if(node && widget.focusFocalNode){
					widget.focusFocalNode(node);
				}else{
					widget.focus();
				}
			}
		},

		_startupChild: function(/*Widget*/ widget){
			// summary:
			//		Set tabindex="-1" on focusable widgets so that we
			// 		can focus them programmatically and by clicking.
			//		Connect focus and blur handlers.
			if(widget.getFocalNodes){
				dojo.forEach(widget.getFocalNodes(), function(node){
					dojo.attr(node, "tabindex", -1);
					this._connectNode(node);
				}, this);
			}else{
				var node = widget.focusNode || widget.domNode;
				if(widget.isFocusable()){
					dojo.attr(node, "tabindex", -1);
				}
				this._connectNode(node);
			}
		},

		_connectNode: function(/*Element*/ node){
			this.connect(node, "onfocus", "_onNodeFocus");
			this.connect(node, "onblur", "_onNodeBlur");
		},

		_onContainerFocus: function(evt){
			// focus bubbles on Firefox,
			// so just make sure that focus has really gone to the container
			if(evt.target === this.domNode){
				this.focusFirstChild();
			}
		},

		_onContainerKeypress: function(evt){
			if(evt.ctrlKey || evt.altKey){ return; }
			var func = this._keyNavCodes[evt.charOrCode];
			if(func){
				func();
				dojo.stopEvent(evt);
			}
		},

		_onNodeFocus: function(evt){
			// while focus is on a child,
			// take the container out of the tab order so that
			// we can shift-tab to the element before the container
			dojo.attr(this.domNode, "tabindex", -1);
			// record the child that has been focused
			var widget = dijit.getEnclosingWidget(evt.target);
			if(widget && widget.isFocusable()){
				this.focusedChild = widget;
			}
			dojo.stopEvent(evt);
		},

		_onNodeBlur: function(evt){
			// when focus leaves a child,
			// reinstate the container's tabindex
			if(this.tabIndex){
				dojo.attr(this.domNode, "tabindex", this.tabIndex);
			}
			dojo.stopEvent(evt);
		},

		_onChildBlur: function(/*Widget*/ widget){
			// summary:
			//		Called when focus leaves a child widget to go
			//		to a sibling widget.
		},

		_getFirstFocusableChild: function(){
			return this._getNextFocusableChild(null, 1);
		},

		_getNextFocusableChild: function(child, dir){
			if(child){
				child = this._getSiblingOfChild(child, dir);
			}
			var children = this.getChildren();
			for(var i=0; i < children.length; i++){
				if(!child){
					child = children[(dir>0) ? 0 : (children.length-1)];
				}
				if(child.isFocusable()){
					return child;
				}
				child = this._getSiblingOfChild(child, dir);
			}
			// no focusable child found
			return null;
		}
	}
);
<commit_msg>Test and fix addChild() for ContentPane and AccordionContainer.  Refs #6842, #7819.   !strict.<commit_after>dojo.provide("dijit._Container");

dojo.declare("dijit._Contained",
	null,
	{
		// summary
		//		Mixin for widgets that are children of a container widget
		//
		// example:
		// | 	// make a basic custom widget that knows about it's parents
		// |	dojo.declare("my.customClass",[dijit._Widget,dijit._Contained],{});
		//
		getParent: function(){
			// summary:
			//		Returns the parent widget of this widget, assuming the parent
			//		implements dijit._Container
			for(var p=this.domNode.parentNode; p; p=p.parentNode){
				var id = p.getAttribute && p.getAttribute("widgetId");
				if(id){
					var parent = dijit.byId(id);
					return parent.isContainer ? parent : null;
				}
			}
			return null;
		},

		_getSibling: function(which){
			var node = this.domNode;
			do{
				node = node[which+"Sibling"];
			}while(node && node.nodeType != 1);
			if(!node){ return null; } // null
			var id = node.getAttribute("widgetId");
			return dijit.byId(id);
		},

		getPreviousSibling: function(){
			// summary:
			//		Returns null if this is the first child of the parent,
			//		otherwise returns the next element sibling to the "left".

			return this._getSibling("previous"); // Mixed
		},

		getNextSibling: function(){
			// summary:
			//		Returns null if this is the last child of the parent,
			//		otherwise returns the next element sibling to the "right".

			return this._getSibling("next"); // Mixed
		},

		getIndexInParent: function(){
			// summary:
			//		Returns the index of this widget within its container parent.
			//		It returns -1 if the parent does not exist, or if the parent
			//		is not a dijit._Container

			var p = this.getParent();
			if(!p || !p.getIndexOfChild){
				return -1; // int
			}
			return p.getIndexOfChild(this); // int
		}
	}
);

dojo.declare("dijit._Container",
	null,
	{
		// summary:
		//		Mixin for widgets that contain a set of widget children.
		// description:
		//		Use this mixin for widgets that needs to know about and
		//		keep track of their widget children. Suitable for widgets like BorderContainer
		//		and TabContainer which contain (only) a set of child widgets.
		//
		//		It's not suitable for widgets like ContentPane
		//		which contains mixed HTML (plain DOM nodes in addition to widgets),
		//		and where contained widgets are not necessarily directly below
		//		this.containerNode.   In that case calls like addChild(node, position)
		//		wouldn't make sense.

		// isContainer: Boolean
		//		Just a flag indicating that this widget descends from dijit._Container
		isContainer: true,

		buildRendering: function(){
			this.inherited(arguments);
			if(!this.containerNode){
				// all widgets with descendants must set containerNode
   				this.containerNode = this.domNode;
			}
		},

		addChild: function(/*Widget*/ widget, /*int?*/ insertIndex){
			// summary:
			//		Makes the given widget a child of this widget.
			// description:
			//		Inserts specified child widget's dom node as a child of this widget's
			//		container node, and possibly does other processing (such as layout).

			var refNode = this.containerNode;
			if(insertIndex && typeof insertIndex == "number"){
				var children = this.getChildren();
				if(children && children.length >= insertIndex){
					refNode = children[insertIndex-1].domNode;
					insertIndex = "after";
				}
			}
			dojo.place(widget.domNode, refNode, insertIndex);

			// If I've been started but the child widget hasn't been started,
			// start it now.  Make sure to do this after widget has been
			// inserted into the DOM tree, so it can see that it's being controlled by me,
			// so it doesn't try to size itself.
			if(this._started && !widget._started){
				widget.startup();
			}
		},

		removeChild: function(/*Widget or int*/ widget){
			// summary:
			//		Removes the passed widget instance from this widget but does
			//		not destroy it.  You can also pass in an integer indicating
			//		the index within the container to remove
			if(typeof widget == "number" && widget > 0){
				widget = this.getChildren()[widget];
			}
			// If we cannot find the widget, just return
			if(!widget || !widget.domNode){ return; }

			var node = widget.domNode;
			node.parentNode.removeChild(node);	// detach but don't destroy
		},

		_nextElement: function(node){
			do{
				node = node.nextSibling;
			}while(node && node.nodeType != 1);
			return node;
		},

		_firstElement: function(node){
			node = node.firstChild;
			if(node && node.nodeType != 1){
				node = this._nextElement(node);
			}
			return node;
		},

		getChildren: function(){
			// summary:
			//		Returns array of children widgets.
			// description:
			//		Returns the widgets that are directly under this.containerNode.
			return dojo.query("> [widgetId]", this.containerNode).map(dijit.byNode); // Widget[]
		},

		hasChildren: function(){
			// summary:
			//		Returns true if widget has children, i.e. if this.containerNode contains something.
			return !!this._firstElement(this.containerNode); // Boolean
		},

		destroyDescendants: function(/*Boolean*/ preserveDom){
			dojo.forEach(this.getChildren(), function(child){ child.destroyRecursive(preserveDom); });
		},

		_getSiblingOfChild: function(/*Widget*/ child, /*int*/ dir){
			// summary:
			//		Get the next or previous widget sibling of child
			// dir:
			//		if 1, get the next sibling
			//		if -1, get the previous sibling
			var node = child.domNode;
			var which = (dir>0 ? "nextSibling" : "previousSibling");
			do{
				node = node[which];
			}while(node && (node.nodeType != 1 || !dijit.byNode(node)));
			return node ? dijit.byNode(node) : null;
		},

		getIndexOfChild: function(/*Widget*/ child){
			// summary:
			//		Gets the index of the child in this container or -1 if not found
			var children = this.getChildren();
			for(var i=0, c; c=children[i]; i++){
				if(c == child){
					return i; // int
				}
			}
			return -1; // int
		}
	}
);

dojo.declare("dijit._KeyNavContainer",
	[dijit._Container],
	{

		// summary: A _Container with keyboard navigation of its children.
		// decscription:
		//		To use this mixin, call connectKeyNavHandlers() in
		//		postCreate() and call startupKeyNavChildren() in startup().
		//		It provides normalized keyboard and focusing code for Container
		//		widgets.
/*=====
		// focusedChild: Widget
		//		The currently focused child widget, or null if there isn't one
		focusedChild: null,
=====*/

		_keyNavCodes: {},

		connectKeyNavHandlers: function(/*Array*/ prevKeyCodes, /*Array*/ nextKeyCodes){
			// summary:
			//		Call in postCreate() to attach the keyboard handlers
			//		to the container.
			// preKeyCodes: Array
			//		Key codes for navigating to the previous child.
			// nextKeyCodes: Array
			//		Key codes for navigating to the next child.

			var keyCodes = this._keyNavCodes = {};
			var prev = dojo.hitch(this, this.focusPrev);
			var next = dojo.hitch(this, this.focusNext);
			dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev });
			dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next });
			this.connect(this.domNode, "onkeypress", "_onContainerKeypress");
			this.connect(this.domNode, "onfocus", "_onContainerFocus");
		},

		startupKeyNavChildren: function(){
			// summary:
			//		Call in startup() to set child tabindexes to -1
			dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild"));
		},

		addChild: function(/*Widget*/ widget, /*int?*/ insertIndex){
			// summary: Add a child to our _Container
			dijit._KeyNavContainer.superclass.addChild.apply(this, arguments);
			this._startupChild(widget);
		},

		focus: function(){
			// summary: Default focus() implementation: focus the first child.
			this.focusFirstChild();
		},

		focusFirstChild: function(){
			// summary: Focus the first focusable child in the container.
			this.focusChild(this._getFirstFocusableChild());
		},

		focusNext: function(){
			// summary: Focus the next widget or focal node (for widgets
			//		with multiple focal nodes) within this container.
			if(this.focusedChild && this.focusedChild.hasNextFocalNode
					&& this.focusedChild.hasNextFocalNode()){
				this.focusedChild.focusNext();
				return;
			}
			var child = this._getNextFocusableChild(this.focusedChild, 1);
			if(child.getFocalNodes){
				this.focusChild(child, child.getFocalNodes()[0]);
			}else{
				this.focusChild(child);
			}
		},

		focusPrev: function(){
			// summary: Focus the previous widget or focal node (for widgets
			//		with multiple focal nodes) within this container.
			if(this.focusedChild && this.focusedChild.hasPrevFocalNode
					&& this.focusedChild.hasPrevFocalNode()){
				this.focusedChild.focusPrev();
				return;
			}
			var child = this._getNextFocusableChild(this.focusedChild, -1);
			if(child.getFocalNodes){
				var nodes = child.getFocalNodes();
				this.focusChild(child, nodes[nodes.length-1]);
			}else{
				this.focusChild(child);
			}
		},

		focusChild: function(/*Widget*/ widget, /*Node?*/ node){
			// summary: Focus widget. Optionally focus 'node' within widget.
			if(widget){
				if(this.focusedChild && widget !== this.focusedChild){
					this._onChildBlur(this.focusedChild);
				}
				this.focusedChild = widget;
				if(node && widget.focusFocalNode){
					widget.focusFocalNode(node);
				}else{
					widget.focus();
				}
			}
		},

		_startupChild: function(/*Widget*/ widget){
			// summary:
			//		Set tabindex="-1" on focusable widgets so that we
			// 		can focus them programmatically and by clicking.
			//		Connect focus and blur handlers.
			if(widget.getFocalNodes){
				dojo.forEach(widget.getFocalNodes(), function(node){
					dojo.attr(node, "tabindex", -1);
					this._connectNode(node);
				}, this);
			}else{
				var node = widget.focusNode || widget.domNode;
				if(widget.isFocusable()){
					dojo.attr(node, "tabindex", -1);
				}
				this._connectNode(node);
			}
		},

		_connectNode: function(/*Element*/ node){
			this.connect(node, "onfocus", "_onNodeFocus");
			this.connect(node, "onblur", "_onNodeBlur");
		},

		_onContainerFocus: function(evt){
			// focus bubbles on Firefox,
			// so just make sure that focus has really gone to the container
			if(evt.target === this.domNode){
				this.focusFirstChild();
			}
		},

		_onContainerKeypress: function(evt){
			if(evt.ctrlKey || evt.altKey){ return; }
			var func = this._keyNavCodes[evt.charOrCode];
			if(func){
				func();
				dojo.stopEvent(evt);
			}
		},

		_onNodeFocus: function(evt){
			// while focus is on a child,
			// take the container out of the tab order so that
			// we can shift-tab to the element before the container
			dojo.attr(this.domNode, "tabindex", -1);
			// record the child that has been focused
			var widget = dijit.getEnclosingWidget(evt.target);
			if(widget && widget.isFocusable()){
				this.focusedChild = widget;
			}
			dojo.stopEvent(evt);
		},

		_onNodeBlur: function(evt){
			// when focus leaves a child,
			// reinstate the container's tabindex
			if(this.tabIndex){
				dojo.attr(this.domNode, "tabindex", this.tabIndex);
			}
			dojo.stopEvent(evt);
		},

		_onChildBlur: function(/*Widget*/ widget){
			// summary:
			//		Called when focus leaves a child widget to go
			//		to a sibling widget.
		},

		_getFirstFocusableChild: function(){
			return this._getNextFocusableChild(null, 1);
		},

		_getNextFocusableChild: function(child, dir){
			if(child){
				child = this._getSiblingOfChild(child, dir);
			}
			var children = this.getChildren();
			for(var i=0; i < children.length; i++){
				if(!child){
					child = children[(dir>0) ? 0 : (children.length-1)];
				}
				if(child.isFocusable()){
					return child;
				}
				child = this._getSiblingOfChild(child, dir);
			}
			// no focusable child found
			return null;
		}
	}
);
<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var bench = require( '@stdlib/bench' );
var randu = require( '@stdlib/random/base/randu' );
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var round = require( '@stdlib/math/base/special/round' );
var pkg = require( './../package.json' ).name;
var cround = require( './../lib' );

// MAIN //

bench( pkg, function benchmark( b ) {
	var re;
	var im;
	var y;
	var i;

	b.tic();
	for ( i = 0; i < b.iterations; i++ ) {
		re = ( randu()*1000.0 ) - 500.0;
		im = ( randu()*1000.0 ) - 500.0;
		y = cround( re, im );
		if ( y.length === 0 ) {
			b.fail( 'should not be empty' );
		}
	}
	b.toc();
	if ( isnan( y ) ) {
		b.fail( 'should not return NaN' );
	}
	b.pass( 'benchmark finished' );
	b.end();
});

bench( pkg+'::memory_reuse', function benchmark( b ) {
	var out;
	var re;
	var im;
	var y;
	var i;

	out = new Array( 2 );

	b.tic();
	for ( i = 0; i < b.iterations; i++ ) {
		re = ( randu()*1000.0 ) - 500.0;
		im = ( randu()*1000.0 ) - 500.0;
		y = cround( out, re, im );
		if ( y.length === 0 ) {
			b.fail( 'should not be empty' );
		}
	}
	b.toc();
	if ( isnan( y ) ) {
		b.fail( 'should not return NaN' );
	}
	b.pass( 'benchmark finished' );
	b.end();
});

bench( pkg+'::manual', function benchmark( b ) {
	var re;
	var im;
	var y;
	var i;

	b.tic();
	for ( i = 0; i < b.iterations; i++ ) {
		re = ( randu()*1000.0 ) - 500.0;
		im = ( randu()*1000.0 ) - 500.0;
		y = [ round( re ), round( im ) ];
		if ( y.length === 0 ) {
			b.fail( 'should not be empty' );
		}
	}
	b.toc();
	if ( isnan( y ) ) {
		b.fail( 'should not return NaN' );
	}
	b.pass( 'benchmark finished' );
	b.end();
});

bench( pkg+'::manual,memory_reuse', function benchmark( b ) {
	var re;
	var im;
	var y;
	var i;

	y = new Array( 2 );

	b.tic();
	for ( i = 0; i < b.iterations; i++ ) {
		re = ( randu()*1000.0 ) - 500.0;
		im = ( randu()*1000.0 ) - 500.0;
		y[ 0 ] = round( re );
		y[ 1 ] = round( im );
		if ( y.length === 0 ) {
			b.fail( 'should not be empty' );
		}
	}
	b.toc();
	if ( isnan( y ) ) {
		b.fail( 'should not return NaN' );
	}
	b.pass( 'benchmark finished' );
	b.end();
});
<commit_msg>Fix assertions<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var bench = require( '@stdlib/bench' );
var randu = require( '@stdlib/random/base/randu' );
var isArray = require( '@stdlib/assert/is-array' );
var round = require( '@stdlib/math/base/special/round' );
var pkg = require( './../package.json' ).name;
var cround = require( './../lib' );

// MAIN //

bench( pkg, function benchmark( b ) {
	var re;
	var im;
	var y;
	var i;

	b.tic();
	for ( i = 0; i < b.iterations; i++ ) {
		re = ( randu()*1000.0 ) - 500.0;
		im = ( randu()*1000.0 ) - 500.0;
		y = cround( re, im );
		if ( y.length === 0 ) {
			b.fail( 'should not be empty' );
		}
	}
	b.toc();
	if ( !isArray( y ) ) {
		b.fail( 'should return an array' );
	}
	b.pass( 'benchmark finished' );
	b.end();
});

bench( pkg+'::memory_reuse', function benchmark( b ) {
	var out;
	var re;
	var im;
	var y;
	var i;

	out = new Array( 2 );

	b.tic();
	for ( i = 0; i < b.iterations; i++ ) {
		re = ( randu()*1000.0 ) - 500.0;
		im = ( randu()*1000.0 ) - 500.0;
		y = cround( out, re, im );
		if ( y.length === 0 ) {
			b.fail( 'should not be empty' );
		}
	}
	b.toc();
	if ( !isArray( y ) ) {
		b.fail( 'should return an array' );
	}
	b.pass( 'benchmark finished' );
	b.end();
});

bench( pkg+'::manual', function benchmark( b ) {
	var re;
	var im;
	var y;
	var i;

	b.tic();
	for ( i = 0; i < b.iterations; i++ ) {
		re = ( randu()*1000.0 ) - 500.0;
		im = ( randu()*1000.0 ) - 500.0;
		y = [ round( re ), round( im ) ];
		if ( y.length === 0 ) {
			b.fail( 'should not be empty' );
		}
	}
	b.toc();
	if ( !isArray( y ) ) {
		b.fail( 'should return an array' );
	}
	b.pass( 'benchmark finished' );
	b.end();
});

bench( pkg+'::manual,memory_reuse', function benchmark( b ) {
	var re;
	var im;
	var y;
	var i;

	y = new Array( 2 );

	b.tic();
	for ( i = 0; i < b.iterations; i++ ) {
		re = ( randu()*1000.0 ) - 500.0;
		im = ( randu()*1000.0 ) - 500.0;
		y[ 0 ] = round( re );
		y[ 1 ] = round( im );
		if ( y.length === 0 ) {
			b.fail( 'should not be empty' );
		}
	}
	b.toc();
	if ( !isArray( y ) ) {
		b.fail( 'should return an array' );
	}
	b.pass( 'benchmark finished' );
	b.end();
});
<|endoftext|>


<commit_before>var util = require('./util.js');
var log = require('./log.js');

var events = {
  messages : {},
  subscribe: function(topic, func, priority, pid){
    var self = this;
    var nPid = pid || 0;
    var nPriority = parseInt(priority);
    nPriority = nPriority ? nPriority : 10; // 默认优先级为10
    var sTopicEncode = encodeURIComponent(topic);
    //
    if(typeof self.messages[sTopicEncode] === 'undefined'){
      self.messages[sTopicEncode] = [];
    }
    if(typeof self.messages[sTopicEncode][nPriority] === 'undefined'){
      self.messages[sTopicEncode][nPriority] = [];
    }
    var aPriorityList = self.messages[sTopicEncode][nPriority];
    var oCallBackConfig = {
      execute: func,
      pid: nPid
    };
    var nFuncIndex = -1;
    if(aPriorityList){
      if(util.inArray(oCallBackConfig, aPriorityList) === - 1){ // 只允许绑定一个相同函数
        aPriorityList.push(oCallBackConfig);
        nFuncIndex = aPriorityList.length - 1;
        log({subscribe: topic, priority: nPriority, sort: nFuncIndex, pid: nPid},"background:#ccc");
      }else{
        log({subscribe: topic, priority: nPriority, sort: '重复监听，忽略', pid: nPid}, "color:red");
      }
    }
    return {
      unsubscribe : function(){
        // 如果有 pid 可能非顺序订阅执行，需要将数组对应项目致空
        if(nPid){
          log({unsubscribe: topic, priority: nPriority, sort: nFuncIndex, pid: nPid},"color:#fff;background:#333");
          if(aPriorityList[nFuncIndex]){
            aPriorityList[nFuncIndex] = null;
          }
          return;
        }
        if(nFuncIndex !== -1){
          log({unsubscribe: topic, priority: nPriority, sort: nFuncIndex, pid: nPid},"color:#fff;background:#333");
          aPriorityList.splice(nFuncIndex, 1);
          nFuncIndex = -1;
        }
      }
    };
  },
  publish : function(topic, data, done, pid){
    var self = this;
    var nPid = pid || 0;
    if(typeof topic !== 'string'){
      return;
    }
    data = data ? data : {};
    var sTopicEncode = encodeURIComponent(topic);
    var aMainList = self.messages[sTopicEncode];
    //
    log({published: topic, pid: nPid}, "color:yellow;background:#000");
    //
    if(typeof aMainList === 'undefined'){
      return;
    }
    var scope = data._scope || this;
    var bNext = true;
    util.forEach(aMainList, function(aPriorityList, nPriority){
      if(aPriorityList){
        util.forEach(aPriorityList, function(oSub, nFuncIndex){
          if(oSub && oSub.execute){
            if(oSub.pid && nPid){ // 监听的事件有pid 并且有传入指定pid
              if(oSub.pid !== nPid){
                log({exec: topic, priority: nPriority, sort: nFuncIndex, 'continue': 'true', pid: oSub.pid, pass: 'true'}, "color:gray");
                return true; // 不执行
              }
            }
            try{
              bNext = oSub.execute.call(scope, data);
            }catch(e){
              log({'exec-error': topic, priority: nPriority, sort: nFuncIndex, 'continue': 'true', pid: oSub.pid, pass: 'true', error: e}, "color:red");
            }
            if(bNext !== false){
              bNext = true;
            }
            log({exec: topic, priority: nPriority, sort: nFuncIndex, 'continue': bNext, pid: nPid}, "color:blue");
            return bNext;
          }
        });
        return bNext;
      }
    });
    if(done){
      done();
    }
  },
};

exports.subscribe = events.subscribe;

exports.publish = events.publish;

exports.messages = events.messages;

/**
 * 发布/订阅事件
 *
 * ```subscribe
 * `` topic
 * &` 类型：String<br/>需要订阅的事件，如'cart.get.done'。
 * `` callback
 * &` 类型：Function( publish参数data )<br/>事件发布后的回调函数，回调函数如果^^^return false^^^,则不继续执行该事件的其他回调函数。
 * `` priority
 * &` 类型：Number ( 默认值：10 )<br/>回调函数执行的优先级，SDK默认的优先级为10，如果要先于默认回调执行，请使用1~9的优先级，如果要后于默认回调执行，请大于10的优先级。
 *
 * 如何取消订阅？
 * ^^^
 * <script>
 *   var s = events.subscribe('hello', function(){
 *     console.log('hello');
 *   }); // 成功订阅后会返回引用
 *   //
 *   s.unsubscribe(); //取消订阅
 * </script>
 * ^^^
 * ```
 *
 * ```publish
 * `` topic
 * &` 类型：String<br/>需要发布的事件，如'cart.get.done'。
 * `` data
 * &` 类型：Object
 * &&` res 类型：Object<br/>请不要使用此保留 key，用于 SDK 从 API 获取数据后传至回调函数。
 * &&` anything 类型：Object 选填<br/>使用除 res 之外 key 的对象，可以向订阅事件传送额外数据。
 * ```
 *
 * @param {subscribe} `topic,callback[,priority]` 订阅事件
 * @param {publish} `topic,data` 发布事件
 *
 */<commit_msg>增加 pid 相关的注释<commit_after>var util = require('./util.js');
var log = require('./log.js');

var events = {
  messages : {},
  subscribe: function(topic, func, priority, pid){
    var self = this;
    var nPid = pid || 0;
    var nPriority = parseInt(priority);
    nPriority = nPriority ? nPriority : 10; // 默认优先级为10
    var sTopicEncode = encodeURIComponent(topic);
    //
    if(typeof self.messages[sTopicEncode] === 'undefined'){
      self.messages[sTopicEncode] = [];
    }
    if(typeof self.messages[sTopicEncode][nPriority] === 'undefined'){
      self.messages[sTopicEncode][nPriority] = [];
    }
    var aPriorityList = self.messages[sTopicEncode][nPriority];
    var oCallBackConfig = {
      execute: func,
      pid: nPid
    };
    var nFuncIndex = -1;
    if(aPriorityList){
      if(util.inArray(oCallBackConfig, aPriorityList) === - 1){ // 只允许绑定一个相同函数
        aPriorityList.push(oCallBackConfig);
        nFuncIndex = aPriorityList.length - 1;
        log({subscribe: topic, priority: nPriority, sort: nFuncIndex, pid: nPid},"background:#ccc");
      }else{
        log({subscribe: topic, priority: nPriority, sort: '重复监听，忽略', pid: nPid}, "color:red");
      }
    }
    return {
      unsubscribe : function(){
        // 如果有 pid 可能非顺序订阅执行，需要将数组对应项目置空
        if(nPid){
          log({unsubscribe: topic, priority: nPriority, sort: nFuncIndex, pid: nPid},"color:#fff;background:#333");
          if(aPriorityList[nFuncIndex]){
            aPriorityList[nFuncIndex] = null;
          }
          return;
        }
        if(nFuncIndex !== -1){
          log({unsubscribe: topic, priority: nPriority, sort: nFuncIndex, pid: nPid},"color:#fff;background:#333");
          aPriorityList.splice(nFuncIndex, 1);
          nFuncIndex = -1;
        }
      }
    };
  },
  publish : function(topic, data, done, pid){
    var self = this;
    var nPid = pid || 0;
    if(typeof topic !== 'string'){
      return;
    }
    data = data ? data : {};
    var sTopicEncode = encodeURIComponent(topic);
    var aMainList = self.messages[sTopicEncode];
    //
    log({published: topic, pid: nPid}, "color:yellow;background:#000");
    //
    if(typeof aMainList === 'undefined'){
      return;
    }
    var scope = data._scope || this;
    var bNext = true;
    util.forEach(aMainList, function(aPriorityList, nPriority){
      if(aPriorityList){
        util.forEach(aPriorityList, function(oSub, nFuncIndex){
          if(oSub && oSub.execute){// 有传pid，就执行对应pid。pid为0 就执行全部 包括有pid
            if(oSub.pid && nPid){ // 监听的事件有pid 并且有传入指定pid
              if(oSub.pid !== nPid){
                log({exec: topic, priority: nPriority, sort: nFuncIndex, 'continue': 'true', pid: oSub.pid, pass: 'true'}, "color:gray");
                return true; // 不执行
              }
            }
            try{
              bNext = oSub.execute.call(scope, data);
            }catch(e){
              log({'exec-error': topic, priority: nPriority, sort: nFuncIndex, 'continue': 'true', pid: oSub.pid, pass: 'true', error: e}, "color:red");
            }
            if(bNext !== false){
              bNext = true;
            }
            log({exec: topic, priority: nPriority, sort: nFuncIndex, 'continue': bNext, pid: nPid}, "color:blue");
            return bNext;
          }
        });
        return bNext;
      }
    });
    if(done){
      done();
    }
  },
};

exports.subscribe = events.subscribe;

exports.publish = events.publish;

exports.messages = events.messages;

/**
 * 发布/订阅事件
 *
 * ```subscribe
 * `` topic
 * &` 类型：String<br/>需要订阅的事件，如'cart.get.done'。
 * `` callback
 * &` 类型：Function( publish参数data )<br/>事件发布后的回调函数，回调函数如果^^^return false^^^,则不继续执行该事件的其他回调函数。
 * `` priority
 * &` 类型：Number ( 默认值：10 )<br/>回调函数执行的优先级，SDK默认的优先级为10，如果要先于默认回调执行，请使用1~9的优先级，如果要后于默认回调执行，请大于10的优先级。
 *
 * 如何取消订阅？
 * ^^^
 * <script>
 *   var s = events.subscribe('hello', function(){
 *     console.log('hello');
 *   }); // 成功订阅后会返回引用
 *   //
 *   s.unsubscribe(); //取消订阅
 * </script>
 * ^^^
 * ```
 *
 * ```publish
 * `` topic
 * &` 类型：String<br/>需要发布的事件，如'cart.get.done'。
 * `` data
 * &` 类型：Object
 * &&` res 类型：Object<br/>请不要使用此保留 key，用于 SDK 从 API 获取数据后传至回调函数。
 * &&` anything 类型：Object 选填<br/>使用除 res 之外 key 的对象，可以向订阅事件传送额外数据。
 * ```
 *
 * @param {subscribe} `topic,callback[,priority]` 订阅事件
 * @param {publish} `topic,data` 发布事件
 *
 */<|endoftext|>


<commit_before>import _ from 'lodash';
const colorMap = {
  feature: '#0277bd',
  action: '#ec407a',
  actions: '#ec407a',
  'initial-state': '#ec407a',
  component: '#f08036',
  components: '#f08036',
  misc: '#8d6e63',
  normal: '#888888',
  routes: '#26a69a',
  plugin: '#4CAF50',
};

const iconMap = {
  features: 'rekit',
  feature: 'book',
  action: 'notification',
  actions: 'notification',
  'initial-state': 'database',
  component: 'appstore-o',
  components: 'appstore-o',
  misc: 'folder',
  folder: 'folder',
  file: 'file',
  routes: 'sharealt',
  plugin: 'plugin',
};

const getFeature = file => {
  return /^src\/features\/\w+\//.test(file) ? file.split('/')[2] : null;
};

export default {
  processProjectData(prjData) {
    const byId = id => prjData.elementById[id];
    const features = Object.values(prjData.elementById).filter(ele => ele.type === 'feature');
    const allRoutesDepsMap = features
      .map(f => `src/features/${f.name}/route.js`)
      .reduce((depsMap, routeFile) => {
        const ele = byId(routeFile);
        if (ele && ele.deps && ele.deps.length) {
          ele.deps.forEach(d => {
            depsMap[d.id] = true;
          });
        }
        return depsMap;
      }, {});

    Object.values(prjData.elementById).forEach(ele => {
      if (ele.type && iconMap[ele.type]) {
        ele.icon = iconMap[ele.type];
        ele.iconColor = colorMap[ele.type];
      }

      if (ele.parts) {
        ele.parts.forEach(part => {
          if (byId(part)) byId(part).owner = ele.id;
        });
      }

      if (ele.type === 'file') {
        switch (ele.ext) {
          case 'js':
          case 'svg':
          case 'less':
            ele.icon = `file_type_${ele.ext}`;
            break;
          case 'png':
          case 'jpg':
          case 'jpeg':
          case 'gif':
            ele.icon = 'file_type_image';
            break;
          default:
            break;
        }
      }

      switch (ele.type) {
        case 'components':
          ele.count = ele.children.length;

          break;
        case 'actions':
          ele.count = ele.children.length - 1;
          if (ele.props && ele.props.isAsync) {
            ele.marks = [{ name: 'A', description: 'Async action', bgColor: '#4fc3f7' }];
          }
          break;
        case 'component':
          // Uses Redux store
          if (ele.props && ele.props.connectToStore) {
            ele.marks = [
              { name: 'C', description: 'Connected to Redux Store', bgColor: '#42bd41' },
            ];
          }

          // Used in react router
          if (ele.parts && ele.parts[0] && allRoutesDepsMap[ele.parts[0]]) {
            ele.isInRoute = true;
            ele.marks = [
              ...(ele.marks || []),
              { name: 'R', description: 'Used in route config', bgColor: '#ffb300' },
            ];
          }
          break;
        case 'action':
          if (ele.props && ele.props.isAsync) {
            ele.marks = [{ name: 'A', description: 'Async action', bgColor: '#4fc3f7' }];
          }
          break;
        case 'routes':
          ele.count = ele.routes.length;
          break;
        case 'file':
        case 'folder':
          ele.feature = getFeature(ele.id);
          break;
        default:
          break;
      }

      if (ele.type === 'components') {
        ele.count = ele.children.length;
      }
      if (ele.type === 'actions') {
        ele.count = ele.children.length - 1;
      }

      const coreDir = `src/features/${ele.name}/core`;
      const uiDir = `src/features/${ele.name}/ui`;
      if (ele.type === 'feature' && byId(coreDir) && byId(uiDir)) {
        ele.icon = iconMap.plugin;
        ele.iconColor = colorMap.plugin;
        const misc = ele.children.pop();
        ele.children.push('v:plugin-core-dir', 'v:plugin-ui-dir', misc);
        _.pull(byId(misc).children, coreDir, uiDir);

        Object.assign(prjData.elementById, {
          'v:plugin-core-dir': {
            name: 'Core',
            target: coreDir,
            type: 'folder-alias',
            icon: 'core',
            children: byId(coreDir).children,
          },
          'v:plugin-ui-dir': {
            name: 'UI',
            target: coreDir,
            type: 'folder-alias',
            icon: 'ui',
            children: byId(uiDir).children,
            iconColor: '#CDDC39',
          },
        });

      }

      if (ele.children && ele.children.forEach) {
        ele.children.map(byId).forEach(c => {
          if (c) c.parent = ele.id;
        });
      }
    });
  },
};
<commit_msg>Fix core and ui folder for a plugin.<commit_after>import _ from 'lodash';
const colorMap = {
  feature: '#0277bd',
  action: '#ec407a',
  actions: '#ec407a',
  'initial-state': '#ec407a',
  component: '#f08036',
  components: '#f08036',
  misc: '#8d6e63',
  normal: '#888888',
  routes: '#26a69a',
  plugin: '#4CAF50',
};

const iconMap = {
  features: 'rekit',
  feature: 'book',
  action: 'notification',
  actions: 'notification',
  'initial-state': 'database',
  component: 'appstore-o',
  components: 'appstore-o',
  misc: 'folder',
  folder: 'folder',
  file: 'file',
  routes: 'sharealt',
  plugin: 'plugin',
};

const getFeature = file => {
  return /^src\/features\/\w+\//.test(file) ? file.split('/')[2] : null;
};

export default {
  processProjectData(prjData) {
    const byId = id => prjData.elementById[id];
    const features = Object.values(prjData.elementById).filter(ele => ele.type === 'feature');
    const allRoutesDepsMap = features
      .map(f => `src/features/${f.name}/route.js`)
      .reduce((depsMap, routeFile) => {
        const ele = byId(routeFile);
        if (ele && ele.deps && ele.deps.length) {
          ele.deps.forEach(d => {
            depsMap[d.id] = true;
          });
        }
        return depsMap;
      }, {});

    Object.values(prjData.elementById).forEach(ele => {
      if (ele.type && iconMap[ele.type]) {
        ele.icon = iconMap[ele.type];
        ele.iconColor = colorMap[ele.type];
      }

      if (ele.parts) {
        ele.parts.forEach(part => {
          if (byId(part)) byId(part).owner = ele.id;
        });
      }

      if (ele.type === 'file') {
        switch (ele.ext) {
          case 'js':
          case 'svg':
          case 'less':
            ele.icon = `file_type_${ele.ext}`;
            break;
          case 'png':
          case 'jpg':
          case 'jpeg':
          case 'gif':
            ele.icon = 'file_type_image';
            break;
          default:
            break;
        }
      }

      switch (ele.type) {
        case 'components':
          ele.count = ele.children.length;

          break;
        case 'actions':
          ele.count = ele.children.length - 1;
          if (ele.props && ele.props.isAsync) {
            ele.marks = [{ name: 'A', description: 'Async action', bgColor: '#4fc3f7' }];
          }
          break;
        case 'component':
          // Uses Redux store
          if (ele.props && ele.props.connectToStore) {
            ele.marks = [
              { name: 'C', description: 'Connected to Redux Store', bgColor: '#42bd41' },
            ];
          }

          // Used in react router
          if (ele.parts && ele.parts[0] && allRoutesDepsMap[ele.parts[0]]) {
            ele.isInRoute = true;
            ele.marks = [
              ...(ele.marks || []),
              { name: 'R', description: 'Used in route config', bgColor: '#ffb300' },
            ];
          }
          break;
        case 'action':
          if (ele.props && ele.props.isAsync) {
            ele.marks = [{ name: 'A', description: 'Async action', bgColor: '#4fc3f7' }];
          }
          break;
        case 'routes':
          ele.count = ele.routes.length;
          break;
        case 'file':
        case 'folder':
          ele.feature = getFeature(ele.id);
          break;
        default:
          break;
      }

      if (ele.type === 'components') {
        ele.count = ele.children.length;
      }
      if (ele.type === 'actions') {
        ele.count = ele.children.length - 1;
      }

      if (ele.type === 'feature') {
        const coreDir = `src/features/${ele.name}/core`;
        const uiDir = `src/features/${ele.name}/ui`;
        if (byId(coreDir) && byId(uiDir)) {
          ele.icon = iconMap.plugin;
          ele.iconColor = colorMap.plugin;
          const misc = ele.children.pop();
          _.pull(byId(misc).children, coreDir, uiDir);

          const coreDirId = `v:${ele.name}-plugin-core-dir`;
          const uiDirId = `v:${ele.name}-plugin-ui-dir`;
          ele.children.push(coreDirId, uiDirId, misc);
          Object.assign(prjData.elementById, {
            [coreDirId]: {
              name: 'Core',
              target: coreDir,
              type: 'folder-alias',
              icon: 'core',
              children: byId(coreDir).children.slice(),
            },
            [uiDirId]: {
              name: 'UI',
              target: uiDir,
              type: 'folder-alias',
              icon: 'ui',
              children: byId(uiDir).children.slice(),
              iconColor: '#CDDC39',
            },
          });
        }
      }

      if (ele.children && ele.children.forEach) {
        ele.children.map(byId).forEach(c => {
          if (c) c.parent = ele.id;
        });
      }
    });
  },
};
<|endoftext|>


<commit_before>export default {
  display: 'flex',
  marginRight: 10,
  backgroundColor: 'rgba(27,31,35,0.05)',
  borderRadius: 20,
  width: 32,
  height: 32,
  alignItems: 'center',
  justifyContent: 'center',
  cursor: 'pointer',
};
<commit_msg>style/reaction: reduce whitespace between reactions<commit_after>export default {
  display: 'flex',
  marginRight: 8,
  backgroundColor: 'rgba(27,31,35,0.05)',
  borderRadius: 20,
  width: 32,
  height: 32,
  alignItems: 'center',
  justifyContent: 'center',
  cursor: 'pointer',
};
<|endoftext|>