1/*
2 * Copyright 2006-2012 Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan A��mus <superstippi@gmx.de>
7 *		John Scipione <jscipione@gmail.com>
8 *		Ingo Weinhold <bonefish@cs.tu-berlin.de>
9 */
10
11#include <ExpressionParser.h>
12
13#include <ctype.h>
14#include <math.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <strings.h>
18
19#include <m_apm.h>
20
21
22static const int32 kMaxDecimalPlaces = 32;
23
24
25enum {
26	TOKEN_NONE					= 0,
27	TOKEN_IDENTIFIER,
28	TOKEN_CONSTANT,
29
30	TOKEN_END_OF_LINE			= '\n',
31
32	TOKEN_PLUS					= '+',
33	TOKEN_MINUS					= '-',
34
35	TOKEN_STAR					= '*',
36	TOKEN_SLASH					= '/',
37	TOKEN_MODULO				= '%',
38
39	TOKEN_POWER					= '^',
40	TOKEN_FACTORIAL				= '!',
41
42	TOKEN_OPENING_BRACKET		= '(',
43	TOKEN_CLOSING_BRACKET		= ')',
44
45	TOKEN_AND					= '&',
46	TOKEN_OR					= '|',
47	TOKEN_NOT					= '~'
48};
49
50
51struct ExpressionParser::Token {
52	Token()
53		: string(""),
54		  type(TOKEN_NONE),
55		  value(0),
56		  position(0)
57	{
58	}
59
60	Token(const Token& other)
61		: string(other.string),
62		  type(other.type),
63		  value(other.value),
64		  position(other.position)
65	{
66	}
67
68	Token(const char* string, int32 length, int32 position, int32 type)
69		: string(string, length),
70		  type(type),
71		  value(0),
72		  position(position)
73	{
74	}
75
76	Token& operator=(const Token& other)
77	{
78		string = other.string;
79		type = other.type;
80		value = other.value;
81		position = other.position;
82		return *this;
83	}
84
85	BString		string;
86	int32		type;
87	MAPM		value;
88
89	int32		position;
90};
91
92
93class ExpressionParser::Tokenizer {
94 public:
95	Tokenizer()
96		: fString(""),
97		  fCurrentChar(NULL),
98		  fCurrentToken(),
99		  fReuseToken(false),
100		  fHexSupport(false),
101		  fDecimalSeparator("."),
102		  fGroupSeparator(",")
103	{
104	}
105
106	void SetSupportHexInput(bool enabled)
107	{
108		fHexSupport = enabled;
109	}
110
111	void SetTo(const char* string)
112	{
113		fString = string;
114		fCurrentChar = fString.String();
115		fCurrentToken = Token();
116		fReuseToken = false;
117	}
118
119	const Token& NextToken()
120	{
121		if (fCurrentToken.type == TOKEN_END_OF_LINE)
122			return fCurrentToken;
123
124		if (fReuseToken) {
125			fReuseToken = false;
126//printf("next token (recycled): '%s'\n", fCurrentToken.string.String());
127			return fCurrentToken;
128		}
129
130		while (*fCurrentChar != 0 && isspace(*fCurrentChar))
131			fCurrentChar++;
132
133		int32 decimalLen = fDecimalSeparator.Length();
134		int32 groupLen = fGroupSeparator.Length();
135
136		if (*fCurrentChar == 0 || decimalLen == 0)
137			return fCurrentToken = Token("", 0, _CurrentPos(), TOKEN_END_OF_LINE);
138
139		if (*fCurrentChar == fDecimalSeparator[0] || isdigit(*fCurrentChar)) {
140			if (fHexSupport && *fCurrentChar == '0' && fCurrentChar[1] == 'x')
141				return _ParseHexNumber();
142
143			BString temp;
144
145			const char* begin = fCurrentChar;
146
147			// optional digits before the comma
148			while (isdigit(*fCurrentChar) ||
149				(groupLen > 0 && *fCurrentChar == fGroupSeparator[0])) {
150				if (groupLen > 0 && *fCurrentChar == fGroupSeparator[0]) {
151					int i = 0;
152					while (i < groupLen && *fCurrentChar == fGroupSeparator[i]) {
153						fCurrentChar++;
154						i++;
155					}
156				} else {
157					temp << *fCurrentChar;
158					fCurrentChar++;
159				}
160			}
161
162			// optional post comma part
163			// (required if there are no digits before the comma)
164			if (*fCurrentChar == fDecimalSeparator[0]) {
165				int i = 0;
166				while (i < decimalLen && *fCurrentChar == fDecimalSeparator[i]) {
167					fCurrentChar++;
168					i++;
169				}
170
171				temp << '.';
172
173				// optional post comma digits
174				while (isdigit(*fCurrentChar)) {
175					temp << *fCurrentChar;
176					fCurrentChar++;
177				}
178			}
179
180			// optional exponent part
181			if (*fCurrentChar == 'E') {
182				temp << *fCurrentChar;
183				fCurrentChar++;
184
185				// optional exponent sign
186				if (*fCurrentChar == '+' || *fCurrentChar == '-') {
187					temp << *fCurrentChar;
188					fCurrentChar++;
189				}
190
191				// required exponent digits
192				if (!isdigit(*fCurrentChar)) {
193					throw ParseException("missing exponent in constant",
194						fCurrentChar - begin);
195				}
196
197				while (isdigit(*fCurrentChar)) {
198					temp << *fCurrentChar;
199					fCurrentChar++;
200				}
201			}
202
203			int32 length = fCurrentChar - begin;
204			BString test = temp;
205			test << "&_";
206			double value;
207			char t[2];
208			int32 matches = sscanf(test.String(), "%lf&%s", &value, t);
209			if (matches != 2) {
210				throw ParseException("error in constant",
211					_CurrentPos() - length);
212			}
213
214			fCurrentToken = Token(begin, length, _CurrentPos() - length,
215				TOKEN_CONSTANT);
216			fCurrentToken.value = temp.String();
217		} else if (isalpha(*fCurrentChar) && *fCurrentChar != 'x') {
218			const char* begin = fCurrentChar;
219			while (*fCurrentChar != 0 && (isalpha(*fCurrentChar)
220				|| isdigit(*fCurrentChar))) {
221				fCurrentChar++;
222			}
223			int32 length = fCurrentChar - begin;
224			fCurrentToken = Token(begin, length, _CurrentPos() - length,
225				TOKEN_IDENTIFIER);
226		} else if (strncmp(fCurrentChar, "��", 2) == 0) {
227			fCurrentToken = Token(fCurrentChar, 2, _CurrentPos() - 1,
228				TOKEN_IDENTIFIER);
229			fCurrentChar += 2;
230		} else {
231			int32 type = TOKEN_NONE;
232
233			switch (*fCurrentChar) {
234				case TOKEN_PLUS:
235				case TOKEN_MINUS:
236				case TOKEN_STAR:
237				case TOKEN_SLASH:
238				case TOKEN_MODULO:
239				case TOKEN_POWER:
240				case TOKEN_FACTORIAL:
241				case TOKEN_OPENING_BRACKET:
242				case TOKEN_CLOSING_BRACKET:
243				case TOKEN_AND:
244				case TOKEN_OR:
245				case TOKEN_NOT:
246				case TOKEN_END_OF_LINE:
247					type = *fCurrentChar;
248					break;
249
250				case '\\':
251				case ':':
252				type = TOKEN_SLASH;
253					break;
254
255				case 'x':
256					if (!fHexSupport) {
257						type = TOKEN_STAR;
258						break;
259					}
260					// fall through
261
262				default:
263					throw ParseException("unexpected character", _CurrentPos());
264			}
265			fCurrentToken = Token(fCurrentChar, 1, _CurrentPos(), type);
266			fCurrentChar++;
267		}
268
269//printf("next token: '%s'\n", fCurrentToken.string.String());
270		return fCurrentToken;
271	}
272
273	void RewindToken()
274	{
275		fReuseToken = true;
276	}
277
278	BString DecimalSeparator()
279	{
280		return fDecimalSeparator;
281	}
282
283	BString GroupSeparator()
284	{
285		return fGroupSeparator;
286	}
287
288	void SetSeparators(BString decimal, BString group)
289	{
290		fDecimalSeparator = decimal;
291		fGroupSeparator = group;
292	}
293
294 private:
295	static bool _IsHexDigit(char c)
296	{
297		return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
298	}
299
300	Token& _ParseHexNumber()
301	{
302		const char* begin = fCurrentChar;
303		fCurrentChar += 2;
304			// skip "0x"
305
306		if (!_IsHexDigit(*fCurrentChar))
307			throw ParseException("expected hex digit", _CurrentPos());
308
309		fCurrentChar++;
310		while (_IsHexDigit(*fCurrentChar))
311			fCurrentChar++;
312
313		int32 length = fCurrentChar - begin;
314		fCurrentToken = Token(begin, length, _CurrentPos() - length,
315			TOKEN_CONSTANT);
316
317		// MAPM has no conversion from long long, so we need to improvise.
318		uint64 value = strtoll(fCurrentToken.string.String(), NULL, 0);
319		if (value <= 0x7fffffff) {
320			fCurrentToken.value = (long)value;
321		} else {
322			fCurrentToken.value = (int)(value >> 60);
323			fCurrentToken.value *= 1 << 30;
324			fCurrentToken.value += (int)((value >> 30) & 0x3fffffff);
325			fCurrentToken.value *= 1 << 30;
326			fCurrentToken.value += (int)(value& 0x3fffffff);
327		}
328
329		return fCurrentToken;
330	}
331
332	int32 _CurrentPos() const
333	{
334		return fCurrentChar - fString.String();
335	}
336
337	BString		fString;
338	const char*	fCurrentChar;
339	Token		fCurrentToken;
340	bool		fReuseToken;
341	bool		fHexSupport;
342	BString		fDecimalSeparator;
343	BString		fGroupSeparator;
344};
345
346
347ExpressionParser::ExpressionParser()
348	:	fTokenizer(new Tokenizer()),
349		fDegreeMode(false)
350{
351}
352
353
354ExpressionParser::~ExpressionParser()
355{
356	delete fTokenizer;
357}
358
359
360bool
361ExpressionParser::DegreeMode()
362{
363	return fDegreeMode;
364}
365
366
367void
368ExpressionParser::SetDegreeMode(bool degrees)
369{
370	fDegreeMode = degrees;
371}
372
373
374void
375ExpressionParser::SetSupportHexInput(bool enabled)
376{
377	fTokenizer->SetSupportHexInput(enabled);
378}
379
380
381BString
382ExpressionParser::Evaluate(const char* expressionString)
383{
384	fTokenizer->SetTo(expressionString);
385
386	MAPM value = _ParseBinary();
387	Token token = fTokenizer->NextToken();
388	if (token.type != TOKEN_END_OF_LINE)
389		throw ParseException("parse error", token.position);
390
391	if (value == 0)
392		return BString("0");
393
394	char* buffer = value.toFixPtStringExp(kMaxDecimalPlaces,
395						'.', 0, 0);
396
397	if (buffer == NULL)
398		throw ParseException("out of memory", 0);
399
400	// remove surplus zeros
401	int32 lastChar = strlen(buffer) - 1;
402	if (strchr(buffer, '.')) {
403		while (buffer[lastChar] == '0')
404			lastChar--;
405
406		if (buffer[lastChar] == '.')
407			lastChar--;
408	}
409
410	BString result(buffer, lastChar + 1);
411	result.Replace(".", fTokenizer->DecimalSeparator(), 1);
412	free(buffer);
413	return result;
414}
415
416
417int64
418ExpressionParser::EvaluateToInt64(const char* expressionString)
419{
420	fTokenizer->SetTo(expressionString);
421
422	MAPM value = _ParseBinary();
423	Token token = fTokenizer->NextToken();
424	if (token.type != TOKEN_END_OF_LINE)
425		throw ParseException("parse error", token.position);
426
427	char buffer[128];
428	value.toIntegerString(buffer);
429
430	return strtoll(buffer, NULL, 0);
431}
432
433
434double
435ExpressionParser::EvaluateToDouble(const char* expressionString)
436{
437	fTokenizer->SetTo(expressionString);
438
439	MAPM value = _ParseBinary();
440	Token token = fTokenizer->NextToken();
441	if (token.type != TOKEN_END_OF_LINE)
442		throw ParseException("parse error", token.position);
443
444	char buffer[1024];
445	value.toString(buffer, sizeof(buffer) - 4);
446
447	return strtod(buffer, NULL);
448}
449
450
451MAPM
452ExpressionParser::_ParseBinary()
453{
454	return _ParseSum();
455	// binary operation appearantly not supported by m_apm library,
456	// should not be too hard to implement though....
457
458//	double value = _ParseSum();
459//
460//	while (true) {
461//		Token token = fTokenizer->NextToken();
462//		switch (token.type) {
463//			case TOKEN_AND:
464//				value = (uint64)value & (uint64)_ParseSum();
465//				break;
466//			case TOKEN_OR:
467//				value = (uint64)value | (uint64)_ParseSum();
468//				break;
469//
470//			default:
471//				fTokenizer->RewindToken();
472//				return value;
473//		}
474//	}
475}
476
477
478MAPM
479ExpressionParser::_ParseSum()
480{
481	// TODO: check isnan()...
482	MAPM value = _ParseProduct();
483
484	while (true) {
485		Token token = fTokenizer->NextToken();
486		switch (token.type) {
487			case TOKEN_PLUS:
488				value = value + _ParseProduct();
489				break;
490			case TOKEN_MINUS:
491				value = value - _ParseProduct();
492				break;
493
494			default:
495				fTokenizer->RewindToken();
496				return _ParseFactorial(value);
497		}
498	}
499}
500
501
502MAPM
503ExpressionParser::_ParseProduct()
504{
505	// TODO: check isnan()...
506	MAPM value = _ParsePower();
507
508	while (true) {
509		Token token = fTokenizer->NextToken();
510		switch (token.type) {
511			case TOKEN_STAR:
512				value = value * _ParsePower();
513				break;
514			case TOKEN_SLASH: {
515				MAPM rhs = _ParsePower();
516				if (rhs == MAPM(0))
517					throw ParseException("division by zero", token.position);
518				value = value / rhs;
519				break;
520			}
521			case TOKEN_MODULO: {
522				MAPM rhs = _ParsePower();
523				if (rhs == MAPM(0))
524					throw ParseException("modulo by zero", token.position);
525				value = value % rhs;
526				break;
527			}
528
529			default:
530				fTokenizer->RewindToken();
531				return _ParseFactorial(value);
532		}
533	}
534}
535
536
537MAPM
538ExpressionParser::_ParsePower()
539{
540	MAPM value = _ParseUnary();
541
542	while (true) {
543		Token token = fTokenizer->NextToken();
544		if (token.type != TOKEN_POWER) {
545			fTokenizer->RewindToken();
546			return _ParseFactorial(value);
547		}
548		value = value.pow(_ParseUnary());
549	}
550}
551
552
553MAPM
554ExpressionParser::_ParseUnary()
555{
556	Token token = fTokenizer->NextToken();
557	if (token.type == TOKEN_END_OF_LINE)
558		throw ParseException("unexpected end of expression", token.position);
559
560	switch (token.type) {
561		case TOKEN_PLUS:
562			return _ParseUnary();
563		case TOKEN_MINUS:
564			return -_ParseUnary();
565// TODO: Implement !
566//		case TOKEN_NOT:
567//			return ~(uint64)_ParseUnary();
568
569		case TOKEN_IDENTIFIER:
570			return _ParseFunction(token);
571
572		default:
573			fTokenizer->RewindToken();
574			return _ParseAtom();
575	}
576
577	return MAPM(0);
578}
579
580
581struct Function {
582	const char*	name;
583	int			argumentCount;
584	void*		function;
585	MAPM		value;
586};
587
588
589void
590ExpressionParser::_InitArguments(MAPM values[], int32 argumentCount)
591{
592	_EatToken(TOKEN_OPENING_BRACKET);
593
594	for (int32 i = 0; i < argumentCount; i++)
595		values[i] = _ParseBinary();
596
597	_EatToken(TOKEN_CLOSING_BRACKET);
598}
599
600
601MAPM
602ExpressionParser::_ParseFunction(const Token& token)
603{
604	if (token.string == "e")
605		return _ParseFactorial(MAPM(MM_E));
606	else if (token.string.ICompare("pi") == 0 || token.string == "��")
607		return _ParseFactorial(MAPM(MM_PI));
608
609	// hard coded cases for different count of arguments
610	// supports functions with 3 arguments at most
611
612	MAPM values[3];
613
614	if (strcasecmp("abs", token.string.String()) == 0) {
615		_InitArguments(values, 1);
616		return _ParseFactorial(values[0].abs());
617	} else if (strcasecmp("acos", token.string.String()) == 0) {
618		_InitArguments(values, 1);
619		if (fDegreeMode)
620			values[0] = values[0] * MM_PI / 180;
621
622		if (values[0] < -1 || values[0] > 1)
623			throw ParseException("out of domain", token.position);
624
625		return _ParseFactorial(values[0].acos());
626	} else if (strcasecmp("asin", token.string.String()) == 0) {
627		_InitArguments(values, 1);
628		if (fDegreeMode)
629			values[0] = values[0] * MM_PI / 180;
630
631		if (values[0] < -1 || values[0] > 1)
632			throw ParseException("out of domain", token.position);
633
634		return _ParseFactorial(values[0].asin());
635	} else if (strcasecmp("atan", token.string.String()) == 0) {
636		_InitArguments(values, 1);
637		if (fDegreeMode)
638			values[0] = values[0] * MM_PI / 180;
639
640		return _ParseFactorial(values[0].atan());
641	} else if (strcasecmp("atan2", token.string.String()) == 0) {
642		_InitArguments(values, 2);
643
644		if (fDegreeMode) {
645			values[0] = values[0] * MM_PI / 180;
646			values[1] = values[1] * MM_PI / 180;
647		}
648
649		return _ParseFactorial(values[0].atan2(values[1]));
650	} else if (strcasecmp("cbrt", token.string.String()) == 0) {
651		_InitArguments(values, 1);
652		return _ParseFactorial(values[0].cbrt());
653	} else if (strcasecmp("ceil", token.string.String()) == 0) {
654		_InitArguments(values, 1);
655		return _ParseFactorial(values[0].ceil());
656	} else if (strcasecmp("cos", token.string.String()) == 0) {
657		_InitArguments(values, 1);
658		if (fDegreeMode)
659			values[0] = values[0] * MM_PI / 180;
660
661		return _ParseFactorial(values[0].cos());
662	} else if (strcasecmp("cosh", token.string.String()) == 0) {
663		_InitArguments(values, 1);
664		// This function always uses radians
665		return _ParseFactorial(values[0].cosh());
666	} else if (strcasecmp("exp", token.string.String()) == 0) {
667		_InitArguments(values, 1);
668		return _ParseFactorial(values[0].exp());
669	} else if (strcasecmp("floor", token.string.String()) == 0) {
670		_InitArguments(values, 1);
671		return _ParseFactorial(values[0].floor());
672	} else if (strcasecmp("ln", token.string.String()) == 0) {
673		_InitArguments(values, 1);
674		if (values[0] <= 0)
675			throw ParseException("out of domain", token.position);
676
677		return _ParseFactorial(values[0].log());
678	} else if (strcasecmp("log", token.string.String()) == 0) {
679		_InitArguments(values, 1);
680		if (values[0] <= 0)
681			throw ParseException("out of domain", token.position);
682
683		return _ParseFactorial(values[0].log10());
684	} else if (strcasecmp("pow", token.string.String()) == 0) {
685		_InitArguments(values, 2);
686		return _ParseFactorial(values[0].pow(values[1]));
687	} else if (strcasecmp("sin", token.string.String()) == 0) {
688		_InitArguments(values, 1);
689		if (fDegreeMode)
690			values[0] = values[0] * MM_PI / 180;
691
692		return _ParseFactorial(values[0].sin());
693	} else if (strcasecmp("sinh", token.string.String()) == 0) {
694		_InitArguments(values, 1);
695		// This function always uses radians
696		return _ParseFactorial(values[0].sinh());
697	} else if (strcasecmp("sqrt", token.string.String()) == 0) {
698		_InitArguments(values, 1);
699		if (values[0] < 0)
700			throw ParseException("out of domain", token.position);
701
702		return _ParseFactorial(values[0].sqrt());
703	} else if (strcasecmp("tan", token.string.String()) == 0) {
704		_InitArguments(values, 1);
705		if (fDegreeMode)
706			values[0] = values[0] * MM_PI / 180;
707
708		MAPM divided_by_half_pi = values[0] / MM_HALF_PI;
709		if (divided_by_half_pi.is_integer() && divided_by_half_pi.is_odd())
710			throw ParseException("out of domain", token.position);
711
712		return _ParseFactorial(values[0].tan());
713	} else if (strcasecmp("tanh", token.string.String()) == 0) {
714		_InitArguments(values, 1);
715		// This function always uses radians
716		return _ParseFactorial(values[0].tanh());
717	}
718
719	throw ParseException("unknown identifier", token.position);
720}
721
722
723MAPM
724ExpressionParser::_ParseAtom()
725{
726	Token token = fTokenizer->NextToken();
727	if (token.type == TOKEN_END_OF_LINE)
728		throw ParseException("unexpected end of expression", token.position);
729
730	if (token.type == TOKEN_CONSTANT)
731		return _ParseFactorial(token.value);
732
733	fTokenizer->RewindToken();
734
735	_EatToken(TOKEN_OPENING_BRACKET);
736
737	MAPM value = _ParseBinary();
738
739	_EatToken(TOKEN_CLOSING_BRACKET);
740
741	return _ParseFactorial(value);
742}
743
744
745MAPM
746ExpressionParser::_ParseFactorial(MAPM value)
747{
748	if (fTokenizer->NextToken().type == TOKEN_FACTORIAL) {
749		fTokenizer->RewindToken();
750		_EatToken(TOKEN_FACTORIAL);
751		if (value < 1000)
752			return value.factorial();
753		else {
754			// Use Stirling's approximation (9 term expansion)
755			// http://en.wikipedia.org/wiki/Stirling%27s_approximation
756			// http://www.wolframalpha.com/input/?i=stirling%27s+series
757			// all constants must fit in a signed long for MAPM
758			// (LONG_MAX = 2147483647)
759			return value.pow(value) / value.exp()
760				* (MAPM(2) * MAPM(MM_PI) * value).sqrt()
761				* (MAPM(1) + (MAPM(1) / (MAPM(12) * value))
762					+ (MAPM(1) / (MAPM(288) * value.pow(2)))
763					- (MAPM(139) / (MAPM(51840) * value.pow(3)))
764					- (MAPM(571) / (MAPM(2488320) * value.pow(4)))
765					+ (MAPM(163879) / (MAPM(209018880) * value.pow(5)))
766						// 2147483647 * 35 + 84869155 = 75246796800
767					+ (MAPM(5246819) / ((MAPM(2147483647) * MAPM(35)
768						+ MAPM(84869155)) * value.pow(6)))
769						// 2147483647 * 420 + 1018429860 = 902961561600
770					- (MAPM(534703531) / ((MAPM(2147483647) * MAPM(420)
771						+ MAPM(1018429860)) * value.pow(7)))
772						// 2147483647 * 2 + 188163965 = 4483131259
773						// 2147483647 * 40366 + 985018798 = 86686309913600
774					- ((MAPM(2147483647) * MAPM(2) + MAPM(188163965))
775						/ ((MAPM(2147483647) * MAPM(40366) + MAPM(985018798))
776							* value.pow(8)))
777						// 2147483647 * 201287 + 1380758682 = 432261921612371
778						// 2147483647 * 239771232 + 1145740896
779						// = 514904800886784000
780					+ ((MAPM(2147483647) * MAPM(201287) + MAPM(1380758682))
781						/ ((MAPM(2147483647) * MAPM(239771232)
782								+ MAPM(1145740896))
783							* value.pow(9))));
784		}
785	}
786
787	fTokenizer->RewindToken();
788	return value;
789}
790
791
792void
793ExpressionParser::_EatToken(int32 type)
794{
795	Token token = fTokenizer->NextToken();
796	if (token.type != type) {
797		BString expected;
798		switch (type) {
799			case TOKEN_IDENTIFIER:
800				expected = "an identifier";
801				break;
802
803			case TOKEN_CONSTANT:
804				expected = "a constant";
805				break;
806
807			case TOKEN_PLUS:
808			case TOKEN_MINUS:
809			case TOKEN_STAR:
810			case TOKEN_MODULO:
811			case TOKEN_POWER:
812			case TOKEN_FACTORIAL:
813			case TOKEN_OPENING_BRACKET:
814			case TOKEN_CLOSING_BRACKET:
815			case TOKEN_AND:
816			case TOKEN_OR:
817			case TOKEN_NOT:
818				expected << "'" << (char)type << "'";
819				break;
820
821			case TOKEN_SLASH:
822				expected = "'/', '\\', or ':'";
823				break;
824
825			case TOKEN_END_OF_LINE:
826				expected = "'\\n'";
827				break;
828		}
829		BString temp;
830		temp << "Expected " << expected.String() << " got '" << token.string << "'";
831		throw ParseException(temp.String(), token.position);
832	}
833}
834
835
836status_t
837ExpressionParser::SetSeparators(BString decimal, BString group)
838{
839	if (decimal == group)
840		return B_ERROR;
841
842	fTokenizer->SetSeparators(decimal, group);
843
844	return B_OK;
845}
846