| | 1 | #include "tads.h" |
| | 2 | #include "t3.h" |
| | 3 | #include "dict.h" |
| | 4 | #include "gramprod.h" |
| | 5 | #include "tok.h" |
| | 6 | #include "bignum.h" |
| | 7 | |
| | 8 | enum token tokOp; |
| | 9 | enum token tokFloat; |
| | 10 | |
| | 11 | dictionary gDict; |
| | 12 | dictionary property funcName; |
| | 13 | |
| | 14 | /* |
| | 15 | * Calc globals |
| | 16 | */ |
| | 17 | calcGlobals: object |
| | 18 | /* |
| | 19 | * input precision - for any number entered with smaller precision, |
| | 20 | * we'll increase the precision to this value |
| | 21 | */ |
| | 22 | inputPrecision = 8 |
| | 23 | |
| | 24 | /* guard digits added to input precision */ |
| | 25 | guardPrecision = 3 |
| | 26 | |
| | 27 | /* number of fractional digits to display for each value */ |
| | 28 | dispFracDigits = 8 |
| | 29 | |
| | 30 | /* maximum number of digits to display for each value */ |
| | 31 | dispMaxDigits = 32 |
| | 32 | |
| | 33 | /* display in scientific notation */ |
| | 34 | dispSci = nil |
| | 35 | ; |
| | 36 | |
| | 37 | |
| | 38 | /* |
| | 39 | * Custom tokenizer for arithmetic expressions |
| | 40 | */ |
| | 41 | CalcTokenizer: Tokenizer |
| | 42 | rules_ = |
| | 43 | [ |
| | 44 | /* skip whitespace */ |
| | 45 | ['[ \t]+', nil, &tokCvtSkip, nil], |
| | 46 | |
| | 47 | /* numbers */ |
| | 48 | ['(%.[0-9]+|[0-9]+(%.[0-9]*)?)([eE][+-]?[0-9]+)?', |
| | 49 | tokFloat, nil, nil], |
| | 50 | |
| | 51 | /* operators */ |
| | 52 | ['[(+*-/)!^?]', tokOp, nil, nil], |
| | 53 | |
| | 54 | /* words */ |
| | 55 | ['[a-zA-Z]+', tokWord, &tokCvtLower, nil] |
| | 56 | ] |
| | 57 | ; |
| | 58 | |
| | 59 | grammar command: expression->expr_ : object |
| | 60 | eval() { return expr_.eval(); } |
| | 61 | ; |
| | 62 | |
| | 63 | class commandWithIntegerOp: object |
| | 64 | op_ = nil // this is the grammar element for the operand |
| | 65 | desc_ = "desc" // this is the command name |
| | 66 | opDesc_ = "operator desc" // description of operator's purpose |
| | 67 | eval() |
| | 68 | { |
| | 69 | try |
| | 70 | { |
| | 71 | local val = toInteger(op_.eval()); |
| | 72 | evalWithOp(val); |
| | 73 | } |
| | 74 | catch (Exception exc) |
| | 75 | { |
| | 76 | "Invalid argument to '<<desc_>>' - please specify |
| | 77 | <<opDesc_>>\n"; |
| | 78 | } |
| | 79 | } |
| | 80 | ; |
| | 81 | |
| | 82 | grammar command: 'sci' number->op_ : commandWithIntegerOp |
| | 83 | desc_ = "sci" |
| | 84 | opDesc_ = "the number of digits to display after the decimal point" |
| | 85 | evalWithOp(val) |
| | 86 | { |
| | 87 | /* set scientific notation */ |
| | 88 | calcGlobals.dispSci = true; |
| | 89 | |
| | 90 | /* note the number of digits after the decimal */ |
| | 91 | calcGlobals.dispFracDigits = val; |
| | 92 | |
| | 93 | /* set the maximum digits */ |
| | 94 | calcGlobals.dispMaxDigits = (val > 32 ? val + 5 : 32); |
| | 95 | |
| | 96 | /* explain the change */ |
| | 97 | "Scientific notation, <<val>> digits after the decimal\n"; |
| | 98 | } |
| | 99 | ; |
| | 100 | |
| | 101 | grammar command: [badness 1] 'sci' *: object |
| | 102 | eval() |
| | 103 | { |
| | 104 | "Please enter in the format 'sci N', where N is the number of |
| | 105 | digits to display after the decimal point.\n"; |
| | 106 | } |
| | 107 | ; |
| | 108 | |
| | 109 | grammar command: 'fix' number->op_ : commandWithIntegerOp |
| | 110 | desc_ = "fix" |
| | 111 | opDesc_ = "the number of digits to display after the decimal point" |
| | 112 | evalWithOp(val) |
| | 113 | { |
| | 114 | /* set non-scientific notation */ |
| | 115 | calcGlobals.dispSci = nil; |
| | 116 | |
| | 117 | /* note the number of digits after the decimal */ |
| | 118 | calcGlobals.dispFracDigits = val; |
| | 119 | |
| | 120 | /* set the maximum digits */ |
| | 121 | calcGlobals.dispMaxDigits = (val > 32 ? val + 12 : 32); |
| | 122 | |
| | 123 | /* explain the change */ |
| | 124 | "Fixed notation, <<val>> digits after the decimal\n"; |
| | 125 | } |
| | 126 | ; |
| | 127 | |
| | 128 | grammar command: [badness 1] 'fix' *: object |
| | 129 | eval() |
| | 130 | { |
| | 131 | "Please enter in the format 'fix N', where N is the number of |
| | 132 | digits to display after the decimal point.\n"; |
| | 133 | } |
| | 134 | ; |
| | 135 | |
| | 136 | grammar command: 'prec' number->op_ : commandWithIntegerOp |
| | 137 | desc_ = "prec" |
| | 138 | opDesc_ = "the default input precision" |
| | 139 | evalWithOp(val) |
| | 140 | { |
| | 141 | /* note the new input precision */ |
| | 142 | calcGlobals.inputPrecision = val; |
| | 143 | |
| | 144 | /* explain the change */ |
| | 145 | "Input precision is now <<val>> digits\n"; |
| | 146 | } |
| | 147 | ; |
| | 148 | |
| | 149 | grammar command: [badness 1] 'prec' *: object |
| | 150 | eval() |
| | 151 | { |
| | 152 | "Please enter in the format 'prec N', where N is the number of |
| | 153 | digits to use for the default input precision.\n"; |
| | 154 | } |
| | 155 | ; |
| | 156 | |
| | 157 | grammar expression: term->val_: object |
| | 158 | eval() { return val_.eval(); } |
| | 159 | ; |
| | 160 | |
| | 161 | grammar term: term->val1_ '+' factor->val2_: object |
| | 162 | eval() { return val1_.eval() + val2_.eval(); } |
| | 163 | ; |
| | 164 | |
| | 165 | grammar term: term->val1_ '-' factor->val2_: object |
| | 166 | eval() { return val1_.eval() - val2_.eval(); } |
| | 167 | ; |
| | 168 | |
| | 169 | grammar term: factor->val_: object |
| | 170 | eval() { return val_.eval(); } |
| | 171 | ; |
| | 172 | |
| | 173 | grammar factor: factor->val1_ '*' power->val2_: object |
| | 174 | eval() { return val1_.eval() * val2_.eval(); } |
| | 175 | ; |
| | 176 | |
| | 177 | grammar factor: factor->val1_ '/' power->val2_: object |
| | 178 | eval() { return val1_.eval() / val2_.eval(); } |
| | 179 | ; |
| | 180 | |
| | 181 | grammar factor: power->val_: object |
| | 182 | eval() { return val_.eval(); } |
| | 183 | ; |
| | 184 | |
| | 185 | grammar power: power->val1_ '^' prefix->val2_: object |
| | 186 | eval() { return val1_.eval().raiseToPower(val2_.eval()); } |
| | 187 | ; |
| | 188 | |
| | 189 | grammar power: prefix->val_: object |
| | 190 | eval() { return val_.eval(); } |
| | 191 | ; |
| | 192 | |
| | 193 | grammar prefix: funcName->func_ '(' expression->expr_ ')': object |
| | 194 | eval() { return gDict.findWord(func_, &funcName)[1].eval(expr_); } |
| | 195 | ; |
| | 196 | |
| | 197 | grammar prefix: funcName->func_ prefix->expr_ : object |
| | 198 | eval() { return gDict.findWord(func_, &funcName)[1].eval(expr_); } |
| | 199 | ; |
| | 200 | |
| | 201 | #define DefFunc(nm, func) \ |
| | 202 | object funcName = #@nm eval(expr) { return expr.eval().func(); } |
| | 203 | |
| | 204 | DefFunc(ln, logE); |
| | 205 | DefFunc(log, log10); |
| | 206 | DefFunc(exp, expE); |
| | 207 | DefFunc(sin, sine); |
| | 208 | DefFunc(cos, cosine); |
| | 209 | DefFunc(tan, tangent); |
| | 210 | DefFunc(asin, arcsine); |
| | 211 | DefFunc(acos, arccosine); |
| | 212 | DefFunc(atan, arctangent); |
| | 213 | DefFunc(sqr, sqrt); |
| | 214 | DefFunc(sqrt, sqrt); |
| | 215 | |
| | 216 | #if 0 |
| | 217 | lnFunc: object |
| | 218 | funcName = 'ln' |
| | 219 | eval(expr) |
| | 220 | { |
| | 221 | return expr.eval().logE(); |
| | 222 | } |
| | 223 | ; |
| | 224 | |
| | 225 | logFunc: object |
| | 226 | funcName = 'log' |
| | 227 | eval(expr) |
| | 228 | { |
| | 229 | return expr.eval().log10(); |
| | 230 | } |
| | 231 | ; |
| | 232 | |
| | 233 | expFunc: object |
| | 234 | funcName = 'exp' |
| | 235 | eval(expr) |
| | 236 | { |
| | 237 | return expr.eval().expE(); |
| | 238 | } |
| | 239 | ; |
| | 240 | |
| | 241 | sinFunc: object |
| | 242 | funcName = 'sin' |
| | 243 | eval(expr) |
| | 244 | { |
| | 245 | return expr.eval().sine(); |
| | 246 | } |
| | 247 | ; |
| | 248 | |
| | 249 | cosFunc: object |
| | 250 | funcName = 'cos' |
| | 251 | eval(expr) |
| | 252 | { |
| | 253 | return expr.eval().cosine(); |
| | 254 | } |
| | 255 | ; |
| | 256 | |
| | 257 | tanFunc: object |
| | 258 | funcName = 'tan' |
| | 259 | eval(expr) |
| | 260 | { |
| | 261 | return expr.eval().tangent(); |
| | 262 | } |
| | 263 | ; |
| | 264 | |
| | 265 | asinFunc: object |
| | 266 | funcName = 'asin' |
| | 267 | eval(expr) |
| | 268 | { |
| | 269 | return expr.eval().arcsine(); |
| | 270 | } |
| | 271 | ; |
| | 272 | |
| | 273 | acosFunc: object |
| | 274 | funcName = 'acos' |
| | 275 | eval(expr) |
| | 276 | { |
| | 277 | return expr.eval().arccosine(); |
| | 278 | } |
| | 279 | ; |
| | 280 | |
| | 281 | atanFunc: object |
| | 282 | funcName = 'atan' |
| | 283 | eval(expr) |
| | 284 | { |
| | 285 | return expr.eval().arctangent(); |
| | 286 | } |
| | 287 | ; |
| | 288 | |
| | 289 | sqrFunc: object |
| | 290 | funcName = 'sqr' |
| | 291 | eval(expr) |
| | 292 | { |
| | 293 | return expr.eval().sqrt(); |
| | 294 | } |
| | 295 | ; |
| | 296 | |
| | 297 | sqrtFunc: object |
| | 298 | funcName = 'atan' |
| | 299 | eval(expr) |
| | 300 | { |
| | 301 | return expr.eval().sqrt(); |
| | 302 | } |
| | 303 | ; |
| | 304 | #endif |
| | 305 | |
| | 306 | grammar prefix: '-' postfix->val_ : object |
| | 307 | eval() { return -val_.eval(); } |
| | 308 | ; |
| | 309 | |
| | 310 | grammar prefix: '+' postfix->val_: object |
| | 311 | eval() { return val_.eval(); } |
| | 312 | ; |
| | 313 | |
| | 314 | grammar prefix: postfix->val_ : object |
| | 315 | eval() { return val_.eval(); } |
| | 316 | ; |
| | 317 | |
| | 318 | grammar postfix: atomic->val_ '!': object |
| | 319 | eval() { return factorial(val_.eval()); } |
| | 320 | ; |
| | 321 | |
| | 322 | grammar postfix: atomic->val_ : object |
| | 323 | eval() { return val_.eval(); } |
| | 324 | ; |
| | 325 | |
| | 326 | factorial(x) |
| | 327 | { |
| | 328 | local prod; |
| | 329 | |
| | 330 | /* |
| | 331 | * do this iteratively rather than recursively, to allow for really |
| | 332 | * big inputs without fear of blowing out the stack |
| | 333 | */ |
| | 334 | for (prod = 1.0 ; x > 1 ; --x) |
| | 335 | prod *= x; |
| | 336 | |
| | 337 | /* return the product */ |
| | 338 | return prod; |
| | 339 | } |
| | 340 | |
| | 341 | grammar atomic: '(' expression->val_ ')': object |
| | 342 | eval() { return val_.eval(); } |
| | 343 | ; |
| | 344 | |
| | 345 | grammar atomic: number->num_ : object |
| | 346 | eval() { return num_.eval(); } |
| | 347 | ; |
| | 348 | |
| | 349 | grammar number: tokFloat->num_ : object |
| | 350 | eval() |
| | 351 | { |
| | 352 | local val; |
| | 353 | |
| | 354 | /* parse the number */ |
| | 355 | val = new BigNumber(num_); |
| | 356 | |
| | 357 | /* if the precision is smaller than the input minimum, increase it */ |
| | 358 | if (val.getPrecision() |
| | 359 | < calcGlobals.inputPrecision + calcGlobals.guardPrecision) |
| | 360 | val = val.setPrecision(calcGlobals.inputPrecision |
| | 361 | + calcGlobals.guardPrecision); |
| | 362 | |
| | 363 | /* return the value */ |
| | 364 | return val; |
| | 365 | } |
| | 366 | ; |
| | 367 | |
| | 368 | grammar atomic: 'e' : object |
| | 369 | eval() |
| | 370 | { |
| | 371 | return BigNumber.getE(calcGlobals.inputPrecision |
| | 372 | + calcGlobals.guardPrecision); |
| | 373 | } |
| | 374 | ; |
| | 375 | |
| | 376 | grammar atomic: 'pi': object |
| | 377 | eval() |
| | 378 | { |
| | 379 | return BigNumber.getPi(calcGlobals.inputPrecision |
| | 380 | + calcGlobals.guardPrecision); |
| | 381 | } |
| | 382 | ; |
| | 383 | |
| | 384 | main(args) |
| | 385 | { |
| | 386 | "T3 Scientific Calculator\n |
| | 387 | Type ?\ for help, type Q or QUIT to quit.\n"; |
| | 388 | |
| | 389 | for (;;) |
| | 390 | { |
| | 391 | local str, toks; |
| | 392 | local match; |
| | 393 | |
| | 394 | /* read a line */ |
| | 395 | "\b>"; |
| | 396 | str = inputLine(); |
| | 397 | |
| | 398 | /* tokenize the string */ |
| | 399 | try |
| | 400 | { |
| | 401 | toks = CalcTokenizer.tokenize(str); |
| | 402 | } |
| | 403 | catch (TokErrorNoMatch err) |
| | 404 | { |
| | 405 | "Invalid character '<<err.remainingStr_.substr(1, 1)>>'\n"; |
| | 406 | continue; |
| | 407 | } |
| | 408 | |
| | 409 | /* if it's 'quit' or 'q', stop */ |
| | 410 | if (toks.length() == 1 |
| | 411 | && (getTokVal(toks[1]) is in ('q', 'quit'))) |
| | 412 | break; |
| | 413 | |
| | 414 | /* if it's '?', show help */ |
| | 415 | if (toks.length() == 1 |
| | 416 | && (getTokVal(toks[1]) == '?')) |
| | 417 | { |
| | 418 | showHelp(); |
| | 419 | continue; |
| | 420 | } |
| | 421 | |
| | 422 | /* parse it */ |
| | 423 | match = command.parseTokens(toks, gDict); |
| | 424 | |
| | 425 | /* if we didn't get anything, say so */ |
| | 426 | if (match.length() == 0) |
| | 427 | { |
| | 428 | "Invalid expression\n"; |
| | 429 | } |
| | 430 | else |
| | 431 | { |
| | 432 | local val; |
| | 433 | local flags; |
| | 434 | |
| | 435 | try |
| | 436 | { |
| | 437 | /* evaluate the expression */ |
| | 438 | val = match[1].eval(); |
| | 439 | |
| | 440 | /* if we got a valid, display it */ |
| | 441 | if (val != nil) |
| | 442 | { |
| | 443 | /* clear the display flags */ |
| | 444 | flags = 0; |
| | 445 | |
| | 446 | /* display in scientific or normal notation as desired */ |
| | 447 | if (calcGlobals.dispSci) |
| | 448 | flags |= BignumExp; |
| | 449 | |
| | 450 | /* display the value */ |
| | 451 | "<<val.formatString(calcGlobals.dispMaxDigits, flags, |
| | 452 | -1, calcGlobals.dispFracDigits)>>\n"; |
| | 453 | } |
| | 454 | } |
| | 455 | catch (Exception exc) |
| | 456 | { |
| | 457 | "Evaluation error: <<exc.displayException()>>\n"; |
| | 458 | } |
| | 459 | } |
| | 460 | } |
| | 461 | } |
| | 462 | |
| | 463 | showHelp() |
| | 464 | { |
| | 465 | "Enter numbers in decimal or scientific notation:\b |
| | 466 | \t3.1415926\n |
| | 467 | \t1.705e-11\b |
| | 468 | Operators, in order of precedence:\b |
| | 469 | \ta ^ b\t\traise a to the power of b\n |
| | 470 | \ta * b\t\tmultiply\n |
| | 471 | \ta / b\t\tdivide\n |
| | 472 | \ta + b\t\tadd\n |
| | 473 | \ta - b\t\tsubtract\n |
| | 474 | \t( a )\t\toverride operator precedence\n |
| | 475 | \b |
| | 476 | Functions:\b |
| | 477 | \tsin(x)\t\ttrigonometric sine\n |
| | 478 | \tcos(x)\t\ttrigonometric cosine\n |
| | 479 | \ttan(x)\t\ttrigonometric tangent\n |
| | 480 | \tasin(x)\t\tarcsine\n |
| | 481 | \tacos(x)\t\tarccosine\n |
| | 482 | \tatan(x)\t\tarctangent\n |
| | 483 | \tln(x)\t\tnatural logarithm\n |
| | 484 | \tlog(x)\t\tcommon (base-10) logarithm\n |
| | 485 | \texp(x)\t\traise e (the base of the natural logarithm) to power\n |
| | 486 | \tsqr(x)\t\tsquare root (sqrt(x) is equivalent)\n |
| | 487 | \b |
| | 488 | Constants:\b |
| | 489 | \tpi\t\t3.14159265\n |
| | 490 | \te\t\t2.7182818\n |
| | 491 | \b"; |
| | 492 | "Commands:\b |
| | 493 | \tquit\t\tturn off the calculator\n |
| | 494 | \tsci N\t\tdisplay scientific notation with N digits after the decimal\n |
| | 495 | \tfix N\t\tdisplay fixed notation with N digits after the decimal\n |
| | 496 | \tprec N\t\tset default input precision to N digits\n |
| | 497 | \b"; |
| | 498 | } |