1 #include "stdafx.h"
2 #include <climits>
3 #include <algorithm>
4 #include "ExpressionEvaluator.h"
5 #include "Console.h"
6 #include "Debugger.h"
7 #include "MemoryDumper.h"
8 #include "Disassembler.h"
9 #include "LabelManager.h"
10 #include "../Utilities/HexUtilities.h"
11
12 const vector<string> ExpressionEvaluator::_binaryOperators = { { "*", "/", "%", "+", "-", "<<", ">>", "<", "<=", ">", ">=", "==", "!=", "&", "^", "|", "&&", "||" } };
13 const vector<int> ExpressionEvaluator::_binaryPrecedence = { { 10, 10, 10, 9, 9, 8, 8, 7, 7, 7, 7, 6, 6, 5, 4, 3, 2, 1 } };
14 const vector<string> ExpressionEvaluator::_unaryOperators = { { "+", "-", "~", "!" } };
15 const vector<int> ExpressionEvaluator::_unaryPrecedence = { { 11, 11, 11, 11 } };
16 const std::unordered_set<string> ExpressionEvaluator::_operators = { { "*", "/", "%", "+", "-", "<<", ">>", "<", "<=", ">", ">=", "==", "!=", "&", "^", "|", "&&", "||", "~", "!", "(", ")", "{", "}", "[", "]" } };
17
IsOperator(string token,int & precedence,bool unaryOperator)18 bool ExpressionEvaluator::IsOperator(string token, int &precedence, bool unaryOperator)
19 {
20 if(unaryOperator) {
21 for(size_t i = 0, len = _unaryOperators.size(); i < len; i++) {
22 if(token.compare(_unaryOperators[i]) == 0) {
23 precedence = _unaryPrecedence[i];
24 return true;
25 }
26 }
27 } else {
28 for(size_t i = 0, len = _binaryOperators.size(); i < len; i++) {
29 if(token.compare(_binaryOperators[i]) == 0) {
30 precedence = _binaryPrecedence[i];
31 return true;
32 }
33 }
34 }
35 return false;
36 }
37
GetOperator(string token,bool unaryOperator)38 EvalOperators ExpressionEvaluator::GetOperator(string token, bool unaryOperator)
39 {
40 if(unaryOperator) {
41 for(size_t i = 0, len = _unaryOperators.size(); i < len; i++) {
42 if(token.compare(_unaryOperators[i]) == 0) {
43 return (EvalOperators)(EvalOperators::Plus + i);
44 }
45 }
46 } else {
47 for(size_t i = 0, len = _binaryOperators.size(); i < len; i++) {
48 if(token.compare(_binaryOperators[i]) == 0) {
49 return (EvalOperators)(EvalOperators::Multiplication + i);
50 }
51 }
52 }
53 return EvalOperators::Addition;
54 }
55
CheckSpecialTokens(string expression,size_t & pos,string & output,ExpressionData & data)56 bool ExpressionEvaluator::CheckSpecialTokens(string expression, size_t &pos, string &output, ExpressionData &data)
57 {
58 string token;
59 size_t initialPos = pos;
60 size_t len = expression.size();
61 do {
62 char c = std::tolower(expression[pos]);
63 if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '@') {
64 //Only letters, numbers and underscore are allowed in code labels
65 token += c;
66 pos++;
67 } else {
68 break;
69 }
70 } while(pos < len);
71
72 if(token == "a") {
73 output += std::to_string((int64_t)EvalValues::RegA);
74 } else if(token == "x") {
75 output += std::to_string((int64_t)EvalValues::RegX);
76 } else if(token == "y") {
77 output += std::to_string((int64_t)EvalValues::RegY);
78 } else if(token == "ps") {
79 output += std::to_string((int64_t)EvalValues::RegPS);
80 } else if(token == "sp") {
81 output += std::to_string((int64_t)EvalValues::RegSP);
82 } else if(token == "pc") {
83 output += std::to_string((int64_t)EvalValues::RegPC);
84 } else if(token == "oppc") {
85 output += std::to_string((int64_t)EvalValues::RegOpPC);
86 } else if(token == "previousoppc") {
87 output += std::to_string((int64_t)EvalValues::PreviousOpPC);
88 } else if(token == "frame") {
89 output += std::to_string((int64_t)EvalValues::PpuFrameCount);
90 } else if(token == "cycle") {
91 output += std::to_string((int64_t)EvalValues::PpuCycle);
92 } else if(token == "scanline") {
93 output += std::to_string((int64_t)EvalValues::PpuScanline);
94 } else if(token == "irq") {
95 output += std::to_string((int64_t)EvalValues::Irq);
96 } else if(token == "nmi") {
97 output += std::to_string((int64_t)EvalValues::Nmi);
98 } else if(token == "verticalblank") {
99 output += std::to_string((int64_t)EvalValues::VerticalBlank);
100 } else if(token == "sprite0hit") {
101 output += std::to_string((int64_t)EvalValues::Sprite0Hit);
102 } else if(token == "spriteoverflow") {
103 output += std::to_string((int64_t)EvalValues::SpriteOverflow);
104 } else if(token == "value") {
105 output += std::to_string((int64_t)EvalValues::Value);
106 } else if(token == "address") {
107 output += std::to_string((int64_t)EvalValues::Address);
108 } else if(token == "romaddress") {
109 output += std::to_string((int64_t)EvalValues::AbsoluteAddress);
110 } else if(token == "iswrite") {
111 output += std::to_string((int64_t)EvalValues::IsWrite);
112 } else if(token == "isread") {
113 output += std::to_string((int64_t)EvalValues::IsRead);
114 } else if(token == "branched") {
115 output += std::to_string((int64_t)EvalValues::Branched);
116 } else {
117 string originalExpression = expression.substr(initialPos, pos - initialPos);
118 bool validLabel = _debugger->GetLabelManager()->ContainsLabel(originalExpression);
119 if(!validLabel) {
120 //Check if a multi-byte label exists for this name
121 string label = originalExpression + "+0";
122 validLabel = _debugger->GetLabelManager()->ContainsLabel(label);
123 }
124
125 if(validLabel) {
126 data.Labels.push_back(originalExpression);
127 output += std::to_string(EvalValues::FirstLabelIndex + data.Labels.size() - 1);
128 } else {
129 return false;
130 }
131 }
132
133 return true;
134 }
135
GetNextToken(string expression,size_t & pos,ExpressionData & data,bool & success)136 string ExpressionEvaluator::GetNextToken(string expression, size_t &pos, ExpressionData &data, bool &success)
137 {
138 string output;
139 success = true;
140
141 char c = std::tolower(expression[pos]);
142 if(c == '$') {
143 //Hex numbers
144 pos++;
145 for(size_t len = expression.size(); pos < len; pos++) {
146 c = std::tolower(expression[pos]);
147 if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
148 output += c;
149 } else {
150 break;
151 }
152 }
153 if(output.empty()) {
154 //No numbers followed the hex mark, this isn't a valid expression
155 success = false;
156 }
157 output = std::to_string((uint32_t)HexUtilities::FromHex(output));
158 } else if(c == '%') {
159 //Binary numbers
160 pos++;
161 for(size_t len = expression.size(); pos < len; pos++) {
162 c = std::tolower(expression[pos]);
163 if(c == '0' || c <= '1') {
164 output += c;
165 } else {
166 break;
167 }
168 }
169 if(output.empty()) {
170 //No numbers followed the binary mark, this isn't a valid expression
171 success = false;
172 }
173
174 uint32_t value = 0;
175 for(size_t i = 0; i < output.size(); i++) {
176 value <<= 1;
177 value |= output[i] == '1' ? 1 : 0;
178 }
179 output = std::to_string(value);
180 } else if(c >= '0' && c <= '9') {
181 //Regular numbers
182 for(size_t len = expression.size(); pos < len; pos++) {
183 c = std::tolower(expression[pos]);
184 if(c >= '0' && c <= '9') {
185 output += c;
186 } else {
187 break;
188 }
189 }
190 } else if((c < 'a' || c > 'z') && c != '_' && c != '@') {
191 //Operators
192 string operatorToken;
193 for(size_t len = expression.size(); pos < len; pos++) {
194 c = std::tolower(expression[pos]);
195 operatorToken += c;
196 if(output.empty() || _operators.find(operatorToken) != _operators.end()) {
197 //If appending the next char results in a valid operator, append it (or if this is the first character)
198 output += c;
199 } else {
200 //Reached the end of the operator, return
201 break;
202 }
203 }
204 } else {
205 //Special tokens and labels
206 success = CheckSpecialTokens(expression, pos, output, data);
207 }
208
209 return output;
210 }
211
ProcessSpecialOperator(EvalOperators evalOp,std::stack<EvalOperators> & opStack,std::stack<int> & precedenceStack,vector<int64_t> & outputQueue)212 bool ExpressionEvaluator::ProcessSpecialOperator(EvalOperators evalOp, std::stack<EvalOperators> &opStack, std::stack<int> &precedenceStack, vector<int64_t> &outputQueue)
213 {
214 if(opStack.empty()) {
215 return false;
216 }
217 while(opStack.top() != evalOp) {
218 outputQueue.push_back(opStack.top());
219 opStack.pop();
220 precedenceStack.pop();
221
222 if(opStack.empty()) {
223 return false;
224 }
225 }
226 if(evalOp != EvalOperators::Parenthesis) {
227 outputQueue.push_back(opStack.top());
228 }
229 opStack.pop();
230 precedenceStack.pop();
231
232 return true;
233 }
234
ToRpn(string expression,ExpressionData & data)235 bool ExpressionEvaluator::ToRpn(string expression, ExpressionData &data)
236 {
237 std::stack<EvalOperators> opStack = std::stack<EvalOperators>();
238 std::stack<int> precedenceStack;
239
240 size_t position = 0;
241 int parenthesisCount = 0;
242 int bracketCount = 0;
243 int braceCount = 0;
244
245 bool previousTokenIsOp = true;
246 bool operatorExpected = false;
247 bool operatorOrEndTokenExpected = false;
248 while(true) {
249 bool success = true;
250 string token = GetNextToken(expression, position, data, success);
251 if(!success) {
252 return false;
253 }
254
255 if(token.empty()) {
256 break;
257 }
258
259 bool requireOperator = operatorExpected;
260 bool requireOperatorOrEndToken = operatorOrEndTokenExpected;
261 bool unaryOperator = previousTokenIsOp;
262
263 operatorExpected = false;
264 operatorOrEndTokenExpected = false;
265 previousTokenIsOp = false;
266
267 int precedence = 0;
268 if(IsOperator(token, precedence, unaryOperator)) {
269 EvalOperators op = GetOperator(token, unaryOperator);
270 bool rightAssociative = unaryOperator;
271 while(!opStack.empty() && ((rightAssociative && precedence < precedenceStack.top()) || (!rightAssociative && precedence <= precedenceStack.top()))) {
272 //Pop operators from the stack until we find something with higher priority (or empty the stack)
273 data.RpnQueue.push_back(opStack.top());
274 opStack.pop();
275 precedenceStack.pop();
276 }
277 opStack.push(op);
278 precedenceStack.push(precedence);
279
280 previousTokenIsOp = true;
281 } else if(requireOperator) {
282 //We needed an operator, and got something else, this isn't a valid expression (e.g "(3)4" or "[$00]22")
283 return false;
284 } else if(requireOperatorOrEndToken && token[0] != ')' && token[0] != ']' && token[0] != '}') {
285 //We needed an operator or close token - this isn't a valid expression (e.g "%1134")
286 return false;
287 } else if(token[0] == '(') {
288 parenthesisCount++;
289 opStack.push(EvalOperators::Parenthesis);
290 precedenceStack.push(0);
291 previousTokenIsOp = true;
292 } else if(token[0] == ')') {
293 parenthesisCount--;
294 if(!ProcessSpecialOperator(EvalOperators::Parenthesis, opStack, precedenceStack, data.RpnQueue)) {
295 return false;
296 }
297 operatorExpected = true;
298 } else if(token[0] == '[') {
299 bracketCount++;
300 opStack.push(EvalOperators::Bracket);
301 precedenceStack.push(0);
302 } else if(token[0] == ']') {
303 bracketCount--;
304 if(!ProcessSpecialOperator(EvalOperators::Bracket, opStack, precedenceStack, data.RpnQueue)) {
305 return false;
306 }
307 operatorExpected = true;
308 } else if(token[0] == '{') {
309 braceCount++;
310 opStack.push(EvalOperators::Braces);
311 precedenceStack.push(0);
312 } else if(token[0] == '}') {
313 braceCount--;
314 if(!ProcessSpecialOperator(EvalOperators::Braces, opStack, precedenceStack, data.RpnQueue)){
315 return false;
316 }
317 operatorExpected = true;
318 } else {
319 if(token[0] < '0' || token[0] > '9') {
320 return false;
321 } else {
322 data.RpnQueue.push_back(std::stoll(token));
323 operatorOrEndTokenExpected = true;
324 }
325 }
326 }
327
328 if(braceCount || bracketCount || parenthesisCount) {
329 //Mismatching number of brackets/braces/parenthesis
330 return false;
331 }
332
333 while(!opStack.empty()) {
334 data.RpnQueue.push_back(opStack.top());
335 opStack.pop();
336 }
337
338 return true;
339 }
340
Evaluate(ExpressionData & data,DebugState & state,EvalResultType & resultType,OperationInfo & operationInfo)341 int32_t ExpressionEvaluator::Evaluate(ExpressionData &data, DebugState &state, EvalResultType &resultType, OperationInfo &operationInfo)
342 {
343 if(data.RpnQueue.empty()) {
344 resultType = EvalResultType::Invalid;
345 return 0;
346 }
347
348 int pos = 0;
349 int64_t right = 0;
350 int64_t left = 0;
351 resultType = EvalResultType::Numeric;
352
353 for(size_t i = 0, len = data.RpnQueue.size(); i < len; i++) {
354 int64_t token = data.RpnQueue[i];
355
356 if(token >= EvalValues::RegA) {
357 //Replace value with a special value
358 if(token >= EvalValues::FirstLabelIndex) {
359 int64_t labelIndex = token - EvalValues::FirstLabelIndex;
360 if((size_t)labelIndex < data.Labels.size()) {
361 token = _debugger->GetLabelManager()->GetLabelRelativeAddress(data.Labels[(uint32_t)labelIndex]);
362 if(token < -1) {
363 //Label doesn't exist, try to find a matching multi-byte label
364 string label = data.Labels[(uint32_t)labelIndex] + "+0";
365 token = _debugger->GetLabelManager()->GetLabelRelativeAddress(label);
366 }
367 } else {
368 token = -2;
369 }
370 if(token < 0) {
371 //Label is no longer valid
372 resultType = token == -1 ? EvalResultType::OutOfScope : EvalResultType::Invalid;
373 return 0;
374 }
375 } else {
376 switch(token) {
377 case EvalValues::RegA: token = state.CPU.A; break;
378 case EvalValues::RegX: token = state.CPU.X; break;
379 case EvalValues::RegY: token = state.CPU.Y; break;
380 case EvalValues::RegSP: token = state.CPU.SP; break;
381 case EvalValues::RegPS: token = state.CPU.PS; break;
382 case EvalValues::RegPC: token = state.CPU.PC; break;
383 case EvalValues::RegOpPC: token = state.CPU.DebugPC; break;
384 case EvalValues::PpuFrameCount: token = state.PPU.FrameCount; break;
385 case EvalValues::PpuCycle: token = state.PPU.Cycle; break;
386 case EvalValues::PpuScanline: token = state.PPU.Scanline; break;
387 case EvalValues::Nmi: token = state.CPU.NMIFlag; resultType = EvalResultType::Boolean; break;
388 case EvalValues::Irq: token = state.CPU.IRQFlag; resultType = EvalResultType::Boolean; break;
389 case EvalValues::Value: token = operationInfo.Value; break;
390 case EvalValues::Address: token = operationInfo.Address; break;
391 case EvalValues::AbsoluteAddress: token = _debugger->GetAbsoluteAddress(operationInfo.Address); break;
392 case EvalValues::IsWrite: token = operationInfo.OperationType == MemoryOperationType::Write || operationInfo.OperationType == MemoryOperationType::DummyWrite; break;
393 case EvalValues::IsRead: token = operationInfo.OperationType == MemoryOperationType::Read || operationInfo.OperationType == MemoryOperationType::DummyRead; break;
394 case EvalValues::PreviousOpPC: token = state.CPU.PreviousDebugPC; break;
395 case EvalValues::Sprite0Hit: token = state.PPU.StatusFlags.Sprite0Hit; resultType = EvalResultType::Boolean; break;
396 case EvalValues::SpriteOverflow: token = state.PPU.StatusFlags.SpriteOverflow; resultType = EvalResultType::Boolean; break;
397 case EvalValues::VerticalBlank: token = state.PPU.StatusFlags.VerticalBlank; resultType = EvalResultType::Boolean; break;
398 case EvalValues::Branched: token = Disassembler::IsJump(_debugger->GetMemoryDumper()->GetMemoryValue(DebugMemoryType::CpuMemory, state.CPU.PreviousDebugPC, true)); resultType = EvalResultType::Boolean; break;
399 }
400 }
401 } else if(token >= EvalOperators::Multiplication) {
402 right = operandStack[--pos];
403 if(pos > 0 && token <= EvalOperators::LogicalOr) {
404 //Only do this for binary operators
405 left = operandStack[--pos];
406 }
407
408 resultType = EvalResultType::Numeric;
409 switch(token) {
410 case EvalOperators::Multiplication: token = left * right; break;
411 case EvalOperators::Division:
412 if(right == 0) {
413 resultType = EvalResultType::DivideBy0;
414 return 0;
415 }
416 token = left / right; break;
417 case EvalOperators::Modulo:
418 if(right == 0) {
419 resultType = EvalResultType::DivideBy0;
420 return 0;
421 }
422 token = left % right;
423 break;
424 case EvalOperators::Addition: token = left + right; break;
425 case EvalOperators::Substration: token = left - right; break;
426 case EvalOperators::ShiftLeft: token = left << right; break;
427 case EvalOperators::ShiftRight: token = left >> right; break;
428 case EvalOperators::SmallerThan: token = left < right; resultType = EvalResultType::Boolean; break;
429 case EvalOperators::SmallerOrEqual: token = left <= right; resultType = EvalResultType::Boolean; break;
430 case EvalOperators::GreaterThan: token = left > right; resultType = EvalResultType::Boolean; break;
431 case EvalOperators::GreaterOrEqual: token = left >= right; resultType = EvalResultType::Boolean; break;
432 case EvalOperators::Equal: token = left == right; resultType = EvalResultType::Boolean; break;
433 case EvalOperators::NotEqual: token = left != right; resultType = EvalResultType::Boolean; break;
434 case EvalOperators::BinaryAnd: token = left & right; break;
435 case EvalOperators::BinaryXor: token = left ^ right; break;
436 case EvalOperators::BinaryOr: token = left | right; break;
437 case EvalOperators::LogicalAnd: token = left && right; resultType = EvalResultType::Boolean; break;
438 case EvalOperators::LogicalOr: token = left || right; resultType = EvalResultType::Boolean; break;
439
440 //Unary operators
441 case EvalOperators::Plus: token = right; break;
442 case EvalOperators::Minus: token = -right; break;
443 case EvalOperators::BinaryNot: token = ~right; break;
444 case EvalOperators::LogicalNot: token = !right; break;
445 case EvalOperators::Bracket: token = _debugger->GetMemoryDumper()->GetMemoryValue(DebugMemoryType::CpuMemory, (uint32_t)right); break;
446 case EvalOperators::Braces: token = _debugger->GetMemoryDumper()->GetMemoryValueWord(DebugMemoryType::CpuMemory, (uint32_t)right); break;
447 default: throw std::runtime_error("Invalid operator");
448 }
449 }
450 operandStack[pos++] = token;
451 }
452 return (int32_t)operandStack[0];
453 }
454
ExpressionEvaluator(Debugger * debugger)455 ExpressionEvaluator::ExpressionEvaluator(Debugger* debugger)
456 {
457 _debugger = debugger;
458 }
459
GetRpnList(string expression,bool & success)460 ExpressionData ExpressionEvaluator::GetRpnList(string expression, bool &success)
461 {
462 ExpressionData* cachedData = PrivateGetRpnList(expression, success);
463 if(cachedData) {
464 return *cachedData;
465 } else {
466 return ExpressionData();
467 }
468 }
469
PrivateGetRpnList(string expression,bool & success)470 ExpressionData* ExpressionEvaluator::PrivateGetRpnList(string expression, bool& success)
471 {
472 ExpressionData *cachedData = nullptr;
473 {
474 LockHandler lock = _cacheLock.AcquireSafe();
475
476 auto result = _cache.find(expression);
477 if(result != _cache.end()) {
478 cachedData = &(result->second);
479 }
480 }
481
482 if(cachedData == nullptr) {
483 string fixedExp = expression;
484 fixedExp.erase(std::remove(fixedExp.begin(), fixedExp.end(), ' '), fixedExp.end());
485 ExpressionData data;
486 success = ToRpn(fixedExp, data);
487 if(success) {
488 LockHandler lock = _cacheLock.AcquireSafe();
489 _cache[expression] = data;
490 cachedData = &_cache[expression];
491 }
492 } else {
493 success = true;
494 }
495
496 return cachedData;
497 }
498
PrivateEvaluate(string expression,DebugState & state,EvalResultType & resultType,OperationInfo & operationInfo,bool & success)499 int32_t ExpressionEvaluator::PrivateEvaluate(string expression, DebugState &state, EvalResultType &resultType, OperationInfo &operationInfo, bool& success)
500 {
501 success = true;
502 ExpressionData *cachedData = PrivateGetRpnList(expression, success);
503
504 if(!success) {
505 resultType = EvalResultType::Invalid;
506 return 0;
507 }
508
509 return Evaluate(*cachedData, state, resultType, operationInfo);
510 }
511
Evaluate(string expression,DebugState & state,EvalResultType & resultType,OperationInfo & operationInfo)512 int32_t ExpressionEvaluator::Evaluate(string expression, DebugState &state, EvalResultType &resultType, OperationInfo &operationInfo)
513 {
514 try {
515 bool success;
516 int32_t result = PrivateEvaluate(expression, state, resultType, operationInfo, success);
517 if(success) {
518 return result;
519 }
520 } catch(std::exception e) {
521 }
522 resultType = EvalResultType::Invalid;
523 return 0;
524 }
525
Validate(string expression)526 bool ExpressionEvaluator::Validate(string expression)
527 {
528 try {
529 DebugState state;
530 EvalResultType type;
531 OperationInfo operationInfo;
532 bool success;
533 PrivateEvaluate(expression, state, type, operationInfo, success);
534 return success;
535 } catch(std::exception e) {
536 return false;
537 }
538 }
539
540 #if _DEBUG
541 #include <assert.h>
RunTests()542 void ExpressionEvaluator::RunTests()
543 {
544 //Some basic unit tests to run in debug mode
545 auto test = [=](string expr, EvalResultType expectedType, int expectedResult) {
546 DebugState state = { 0 };
547 OperationInfo opInfo = { 0 };
548 EvalResultType type;
549 int32_t result = Evaluate(expr, state, type, opInfo);
550
551 assert(type == expectedType);
552 assert(result == expectedResult);
553 };
554
555 test("1 - -1", EvalResultType::Numeric, 2);
556 test("1 - (-1)", EvalResultType::Numeric, 2);
557 test("1 - -(-1)", EvalResultType::Numeric, 0);
558 test("(0 - 1) == -1 && 5 < 10", EvalResultType::Boolean, true);
559 test("(0 - 1) == 0 || 5 < 10", EvalResultType::Boolean, true);
560 test("(0 - 1) == 0 || 5 < -10", EvalResultType::Boolean, false);
561 test("(0 - 1) == 0 || 15 < 10", EvalResultType::Boolean, false);
562
563 test("10 != $10", EvalResultType::Boolean, true);
564 test("10 == $A", EvalResultType::Boolean, true);
565 test("10 == $0A", EvalResultType::Boolean, true);
566
567 test("(0 - 1 == 0 || 15 < 10", EvalResultType::Invalid, 0);
568 test("10 / 0", EvalResultType::DivideBy0, 0);
569
570 test("x + 5", EvalResultType::Numeric, 5);
571 test("x == 0", EvalResultType::Boolean, true);
572 test("x == y", EvalResultType::Boolean, true);
573 test("x == y == scanline", EvalResultType::Boolean, false); //because (x == y) is true, and true != scanline
574 test("x == y && !(a == x)", EvalResultType::Boolean, false);
575
576 test("(~0 & ~1) & $FFF == $FFE", EvalResultType::Numeric, 0); //because of operator priority (& is done after ==)
577 test("((~0 & ~1) & $FFF) == $FFE", EvalResultType::Boolean, true);
578
579 test("1+3*3+10/(3+4)", EvalResultType::Numeric, 11);
580 test("(1+3*3+10)/(3+4)", EvalResultType::Numeric, 2);
581 test("(1+3*3+10)/3+4", EvalResultType::Numeric, 10);
582
583 test("{$4500}", EvalResultType::Numeric, 0x4545);
584 test("[$4500]", EvalResultType::Numeric, 0x45);
585
586 test("[$45]3", EvalResultType::Invalid, 0);
587 test("($45)3", EvalResultType::Invalid, 0);
588 test("($45]", EvalResultType::Invalid, 0);
589
590 test("%11", EvalResultType::Numeric, 3);
591 test("%011", EvalResultType::Numeric, 3);
592 test("%1011", EvalResultType::Numeric, 11);
593 test("%12", EvalResultType::Invalid, 0);
594 }
595 #endif