private static IEnumerable<Expression> ParseCommaSeparatedList(IReadOnlyList<Token> tokens, Dictionary<string, Expression> variableMap)
		{
			var currentElement = new List<Token>();
			var tokenGroups = new List<List<Token>>();

			for (var i = 0; i < tokens.Count; i++)
			{
				var token = tokens[i];
				if (token.Type == TokenType.Comma)
				{
					tokenGroups.Add(currentElement.Select(e => e).ToList()); // move a copy of the list, not a reference
					currentElement.Clear();
				}

				else if (token.Type == TokenType.OpenBrace)
				{
					var indexOfMatchingBrace = IndexOfMatchingBrace(tokens, i);
					var tokenSubList = tokens.Skip(i + 1).Take(indexOfMatchingBrace - i - 1).ToList();
					var innerList = ParseCommaSeparatedList(tokenSubList, variableMap).ToList();
					var list = new ExpressionListToken(new ExpressionList(innerList));
					currentElement.Add(list);
					i = indexOfMatchingBrace;
				}

				else if (token.Type == TokenType.CloseBrace)
				{
					throw new SyntaxErrorException("Mismatched braces");
				}

				else
				{
					currentElement.Add(token);
				}
			}

			tokenGroups.Add(currentElement.Select(e => e).ToList()); // move a copy of the list
			currentElement.Clear();

			return tokenGroups.Select(tokenGroup => ParsePostfix(CreatePostfixTokenList(tokenGroup, variableMap), variableMap));
		}
		private static IEnumerable<Token> CreatePostfixTokenList(List<Token> tokens, Dictionary<string, Expression> variableMap)
		{
			tokens = InsertImpliedMultiplication(tokens);

			// https://en.wikipedia.org/wiki/Shunting-yard_algorithm

			var outputQueue = new List<Token>();
			var stack = new Stack<Token>();

			for (var index = 0; index < tokens.Count; index++)
			{
				var currentToken = tokens[index];

				// If the token is a number, then add it to the output queue.
				if (currentToken.Type.IsOperand())
				{
					outputQueue.Add(currentToken);
				}

				// If the token is a function token, then push it onto the stack.
				else if (currentToken.Type == TokenType.Function)
				{
					if (FunctionRepository.Get(currentToken.Value).FixType == FixType.PreFix)
					{
						outputQueue.Add(currentToken);
					}
					else
					{
						stack.Push(currentToken);
					}
				}

				// If the token is a function argument separator (e.g., a comma):
				else if (currentToken.Type == TokenType.Comma)
				{
					// Until the token at the top of the stack is a left parenthesis,
					// pop operators off the stack onto the output queue.
					while (stack.Peek().Type != TokenType.OpenParenthesis)
					{
						outputQueue.Add(stack.Pop());
					}

					// If no left parentheses are encountered, either the separator
					// was misplaced or parentheses were mismatched.
				}

				// If the token is an operator, o1, then:
				else if (currentToken.Type.IsOperator())
				{
					// while there is an operator token o2, or a function token fun,
					// at the top of the operator stack:
					while (stack.Any() && (stack.Peek().Type.IsOperator() || stack.Peek().Type == TokenType.Function))
					{
						var somethingChanged = false;

						// if it is a function token then pop fun off the operator 
						// stack, onto the output queue;
						if (stack.Peek().Type == TokenType.Function)
						{
							outputQueue.Add(stack.Pop());
							somethingChanged = true;
						}

						// if on the other hand it is an operator token, and either
						//    o1 is left-associative and its precedence is less than or equal to that of o2, or
						//    o1 is right associative, and has precedence less than that of o2,
						// then pop o2 off the operator stack, onto the output queue;
						else
						{
							var topType = stack.Peek().Type;
							if (topType.IsOperator())
							{
								var o1Associativity = currentToken.Type.Associativity();
								var o1Precedence = currentToken.Type.Precedence();
								var o2Precedence = topType.Precedence();

								if ((o1Associativity == OperatorAssociativity.Left && o1Precedence <= o2Precedence) ||
								    (o1Associativity == OperatorAssociativity.Right && o1Precedence < o2Precedence))
								{
									outputQueue.Add(stack.Pop());
									somethingChanged = true;
								}
							}
						}

						if (!somethingChanged) break;
					}

					// at the end of iteration push o1 onto the operator stack.
					stack.Push(currentToken);
				}

				// If the token is a left parenthesis (i.e. "("), then push it onto the stack.
				else if (currentToken.Type == TokenType.OpenParenthesis)
				{
					stack.Push(currentToken);
				}

				// If the token is a right parenthesis (i.e. ")"):
				else if (currentToken.Type == TokenType.CloseParenthesis)
				{
					// Until the token at the top of the stack is a left parenthesis,
					// pop operators off the stack onto the output queue.
					while (stack.Any() && stack.Peek().Type != TokenType.OpenParenthesis)
					{
						outputQueue.Add(stack.Pop());
					}

					// Pop the left parenthesis from the stack, but not onto the output queue.
					stack.Pop();

					// If the token at the top of the stack is a function token, pop it onto the output queue.
					if (stack.Any() && stack.Peek().Type == TokenType.Function)
					{
						outputQueue.Add(stack.Pop());
					}

					// If the stack runs out without finding a left parenthesis,
					// then there are mismatched parentheses.
				}

				else if (currentToken.Type == TokenType.OpenBrace)
				{
					var indexOfCloseBrace = tokens.FindLastIndex(t => t.Type == TokenType.CloseBrace);
					var tokenSubstring = tokens.Skip(index + 1).Take(indexOfCloseBrace - index - 1).ToList();

					if (tokenSubstring.Count > 0)
					{
						var operands = ParseCommaSeparatedList(tokenSubstring, variableMap).ToList();
						var list = new ExpressionListToken(new ExpressionList(operands));
						outputQueue.Add(list);
					}
					else
					{	// Special case for empty lists
						outputQueue.Add(new ExpressionListToken(new ExpressionList(new List<Expression>())));
					}

					index = indexOfCloseBrace;
				}

				else if (currentToken.Type == TokenType.CloseBrace)
				{
					throw new SyntaxErrorException("Mismatched braces");
				}

				else
				{
					throw new SyntaxErrorException("Unexpected token", currentToken.Value);
				}
			}

			// When there are no more tokens to read:
			// While there are still operator tokens in the stack:
			while (stack.Any())
			{
				// If the operator token on the top of the stack is a parenthesis,
				// then there are mismatched parentheses.
				if (stack.Peek().Type == TokenType.OpenParenthesis || stack.Peek().Type == TokenType.CloseParenthesis)
				{
					throw new SyntaxErrorException("Mismatched parentheses");
				}

				// Pop the operator onto the output queue.
				outputQueue.Add(stack.Pop());
			}

			return outputQueue;
		}