├── .gitignore ├── dm42coder.py └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.ipynb 2 | .ipynb_checkpoints 3 | -------------------------------------------------------------------------------- /dm42coder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | 5 | import os, sys 6 | import re 7 | import binascii 8 | import argparse 9 | import fileinput 10 | 11 | __author__ = "Tim Gray" 12 | __version__ = "1.0" 13 | 14 | fixed = {"CLX": "77", 15 | "ENTER": "83", 16 | "X<>Y": "71", 17 | "R↓": "75", 18 | "+/-": "54", 19 | "±": "54", 20 | "/": "43", 21 | "÷": "43", 22 | # "x": "42", 23 | "×": "42", 24 | "-": "41", 25 | "+": "40", 26 | "LASTX": "76", 27 | "SIN": "59", 28 | "COS": "5A", 29 | "TAN": "5B", 30 | "ASIN": "5C", 31 | "ACOS": "5D", 32 | "ATAN": "5E", 33 | "LOG": "56", 34 | "10^X": "57", 35 | "10↑X": "57", 36 | "LN": "50", 37 | "E^X": "55", 38 | "E↑X": "55", 39 | "SQRT": "52", 40 | "X^2": "51", 41 | "X↑2": "51", 42 | "1/X": "60", 43 | "1÷X": "60", 44 | # "Y^X": "53", 45 | "Y↑X": "53", 46 | "%": "4C", 47 | "PI": "72", 48 | "COMPLEX": "A0 72", 49 | "ALL": "A2 5D", 50 | "NULL": "00", 51 | "CLA": "87", 52 | "DEG": "80", 53 | "RAD": "81", 54 | "GRAD": "82", 55 | "RECT": "A2 5A", 56 | "POLAR": "A2 59", 57 | "CPXRES": "A2 6A", 58 | "REALRES": "A2 6B", 59 | "KEYASN": "A2 63", 60 | "LCLBL": "A2 64", 61 | "RDX.": "A2 5B", 62 | "RDX,": "A2 5C", 63 | "CLΣ": "70", 64 | "CLST": "73", 65 | "CLRG": "8A", 66 | "CLKEYS": "A2 62", 67 | "CLLCD": "A7 63", 68 | "CLMENU": "A2 6D", 69 | # "->DEG": "6B", 70 | # "->RAD": "6A", 71 | # "->HR": "6D", 72 | # "->HMS": "6C", 73 | # "->REC": "4E", 74 | # "->POL": "4F", 75 | "→DEG": "6B", 76 | "→RAD": "6A", 77 | "→HR": "6D", 78 | "→HMS": "6C", 79 | "→REC": "4E", 80 | "→POL": "4F", 81 | "IP": "68", 82 | "FP": "69", 83 | "RND": "6E", 84 | "ABS": "61", 85 | "SIGN": "7A", 86 | "MOD": "4B", 87 | "COMB": "A0 6F", 88 | "PERM": "A0 70", 89 | "N!" : "62", 90 | "GAMMA": "A0 74", 91 | "RAN": "A0 71", 92 | "SEED": "A0 73", 93 | "RTN": "85", 94 | "AVIEW": "7E", 95 | "PROMPT": "8E", 96 | "PSE": "89", 97 | "AIP": "A6 31", 98 | "XTOA": "A6 6F", 99 | "AGRAPH": "A7 64", 100 | "PIXEL": "A7 65", 101 | "BEEP": "86", 102 | "GETKEY": "A2 6E", 103 | "MENU": "A2 5E", 104 | "X=0?": "67", 105 | "X≠0?": "63", 106 | "X<0?": "66", 107 | "X>0?": "64", 108 | "X≤0?": "7B", 109 | "X≥0?": "A2 5F", 110 | "X=Y?": "78", 111 | "X≠Y?": "79", 112 | "XY?": "45", 114 | "X≤Y?": "46", 115 | "X≥Y?": "A2 60", 116 | "PRΣ": "A7 52", 117 | "PRSTK": "A7 53", 118 | "PRA": "A7 48", 119 | "PRX": "A7 54", 120 | "PRUSR": "A7 61", 121 | "ADV": "8F", 122 | "PRLCD": "A7 62", 123 | "DELAY": "A7 60", 124 | "PRON": "A7 5E", 125 | "PROFF": "A7 5F", 126 | "MAN": "A7 5B", 127 | "NORM": "A7 5C", 128 | "TRACE": "A7 5D", 129 | "Σ+": "47", 130 | "Σ-": "48", 131 | "END": "C0 00 0D", 132 | ".END.": "C0 00 0D", 133 | "STOP": "84", 134 | "NEWMAT": "A6 DA", 135 | "R↑": "74", 136 | "REAL?": "A2 65", 137 | "CPX?": "A2 67", 138 | "STR?": "A2 68", 139 | "MAT?": "A2 66", 140 | "DIM?": "A6 E7", 141 | "ON": "A2 70", 142 | "OFF": "8D", 143 | "ΣREG?": "A6 78", 144 | "CLD": "7F", 145 | "ACOSH": "A0 66", 146 | "ALENG": "A6 41", 147 | "ALLΣ": "A0 AE", 148 | "AND": "A5 88", 149 | "AOFF": "8B", 150 | "AON": "8C", 151 | "AROT": "A6 46", 152 | "ASHF": "88", 153 | "ASINH": "A0 64", 154 | "ATANH": "A0 65", 155 | "ATOX": "A6 47", 156 | "BASE+": "A0 E6", 157 | "BASE-": "A0 E7", 158 | # "BASEx": "A0 E8", 159 | "BASE×": "A0 E8", 160 | "BASE÷": "A0 E9", 161 | "BASE±": "A0 EA", 162 | "BEST": "A0 9F", 163 | "BINM": "A0 E5", 164 | "BIT?": "A5 8C", 165 | "CORR": "A0 A7", 166 | "COSH": "A0 62", 167 | "CROSS": "A6 CA", 168 | "CUSTOM": "A2 6F", 169 | "DECM": "A0 E3", 170 | "DELR": "A0 AB", 171 | "DET": "A6 CC", 172 | "DOT": "A6 CB", 173 | "EDIT": "A6 E1", 174 | "EXITALL": "A2 6C", 175 | "EXPF": "A0 A0", 176 | "E^X-1": "58", 177 | "E↑X-1": "58", 178 | "FCSTX": "A0 A8", 179 | "FCSTY": "A0 A9", 180 | "FNRM": "A6 CF", 181 | "GETM": "A6 E8", 182 | "GROW": "A6 E3", 183 | "HEXM": "A0 E2", 184 | "HMS+": "49", 185 | "HMS-": "4A", 186 | "I+": "A6 D2", 187 | "I-": "A6 D3", 188 | "INSR": "A0 AA", 189 | "INVRT": "A6 CE", 190 | "J+": "A6 D4", 191 | "J-": "A6 D5", 192 | "LINF": "A0 A1", 193 | "LINΣ": "A0 AD", 194 | "LN1+X": "65", 195 | "LOGF": "A0 A2", 196 | "MEAN": "7C", 197 | "NOT": "A5 87", 198 | "OCTM": "A0 E4", 199 | "OLD": "A6 DB", 200 | "OR": "A5 89", 201 | "POSA": "A6 5C", 202 | "PUTM": "A6 E9", 203 | "PWRF": "A0 A3", 204 | "RCLEL": "A6 D7", 205 | "RCLIJ": "A6 D9", 206 | "RNRM": "A6 ED", 207 | "ROTXY": "A5 8B", 208 | "RSUM": "A6 D0", 209 | "R<>R": "A6 D1", 210 | "SDEV": "7D", 211 | "SINH": "A0 61", 212 | "SLOPE": "A0 A4", 213 | "STOEL": "A6 D6", 214 | "STOIJ": "A6 D8", 215 | "SUM": "A0 A5", 216 | "TANH": "A0 63", 217 | "TRANS": "A6 C9", 218 | "UVEC": "A6 CD", 219 | "WMEAN": "A0 AC", 220 | "WRAP": "A6 E2", 221 | "XOR": "A5 8A", 222 | "YINT": "A0 A6", 223 | "->DEC": "5F", 224 | "->OCT": "6F", 225 | "→DEC": "5F", 226 | "→OCT": "6F", 227 | "<-": "A6 DC", 228 | "^": "A6 DE", 229 | "v": "A6 DF", 230 | "->": "A6 DD", 231 | "←": "A6 DC", 232 | "↑": "A6 DE", 233 | "↓": "A6 DF", 234 | "→": "A6 DD", 235 | "%CH": "4D", 236 | # "`str`" : "Fn", 237 | "[MIN]": "A6 EA", 238 | "[MAX]": "A6 EB", 239 | "[FIND]": "A6 EC", 240 | "ADATE": "A6 81", 241 | "ATIME": "A6 84", 242 | "ATIME24": "A6 85", 243 | "CLK12": "A6 86", 244 | "CLK24": "A6 87", 245 | "DATE": "A6 8C", 246 | "DATE+": "A6 8D", 247 | "DDAYS": "A6 8E", 248 | "DMY": "A6 8F", 249 | "DOW": "A6 90", 250 | "MDY": "A6 91", 251 | "TIME": "A6 9C" 252 | } 253 | 254 | chars = { 255 | '÷': 0, 256 | '×': 1, 257 | '√': 2, 258 | '∫': 3, 259 | '░': 4, 260 | 'Σ': 5, 261 | '▶': 6, 262 | 'π': 7, 263 | '¿': 8, 264 | '≤': 9, 265 | '\\\[LF\\\]': 10, # for [LF] 266 | '␊': 10, # for [LF] 267 | '≥': 11, 268 | '≠': 12, 269 | '↵': 13, 270 | '↓': 14, 271 | '→': 15, 272 | '←': 16, 273 | 'µ': 17, 274 | 'μ': 17, 275 | '£': 18, 276 | '₤': 18, 277 | '°': 19, 278 | 'Å': 20, 279 | 'Ñ': 21, 280 | 'Ä': 22, 281 | '∡': 23, 282 | 'ᴇ': 24, 283 | 'Æ': 25, 284 | '…': 26, 285 | '␛': 27, 286 | 'Ö': 28, 287 | 'Ü': 29, 288 | '▒': 30, 289 | '■': 31, 290 | '•': 31, 291 | " ": 32, 292 | "!": 33, 293 | '"': 34, 294 | "#": 35, 295 | "$": 36, 296 | "%": 37, 297 | "&": 38, 298 | "'": 39, 299 | "(": 40, 300 | ")": 41, 301 | "*": 42, 302 | "+": 43, 303 | ",": 44, 304 | "-": 45, 305 | ".": 46, 306 | "/": 47, 307 | "0": 48, 308 | "1": 49, 309 | "2": 50, 310 | "3": 51, 311 | "4": 52, 312 | "5": 53, 313 | "6": 54, 314 | "7": 55, 315 | "8": 56, 316 | "9": 57, 317 | ":": 58, 318 | ";": 59, 319 | "<": 60, 320 | "=": 61, 321 | ">": 62, 322 | "?": 63, 323 | "@": 64, 324 | "A": 65, 325 | "B": 66, 326 | "C": 67, 327 | "D": 68, 328 | "E": 69, 329 | "F": 70, 330 | "G": 71, 331 | "H": 72, 332 | "I": 73, 333 | "J": 74, 334 | "K": 75, 335 | "L": 76, 336 | "M": 77, 337 | "N": 78, 338 | "O": 79, 339 | "P": 80, 340 | "Q": 81, 341 | "R": 82, 342 | "S": 83, 343 | "T": 84, 344 | "U": 85, 345 | "V": 86, 346 | "W": 87, 347 | "X": 88, 348 | "Y": 89, 349 | "Z": 90, 350 | "[": 91, 351 | '\\\\': 92, # for \ 352 | "]": 93, 353 | '↑': 94, 354 | "_": 95, 355 | "`": 96, 356 | "a": 97, 357 | "b": 98, 358 | "c": 99, 359 | "d": 100, 360 | "e": 101, 361 | "f": 102, 362 | "g": 103, 363 | "h": 104, 364 | "i": 105, 365 | "j": 106, 366 | "k": 107, 367 | "l": 108, 368 | "m": 109, 369 | "n": 110, 370 | "o": 111, 371 | "p": 112, 372 | "q": 113, 373 | "r": 114, 374 | "s": 115, 375 | "t": 116, 376 | "u": 117, 377 | "v": 118, 378 | "w": 119, 379 | "x": 120, 380 | "y": 121, 381 | "z": 122, 382 | "{": 123, 383 | "|": 124, 384 | "}": 125, 385 | "~": 126, 386 | "⊦": 127 387 | } 388 | 389 | stacks = {'T': 0, 390 | 'Z': 1, 391 | 'Y': 2, 392 | 'X': 3, 393 | 'L': 4} 394 | 395 | modops = [ 396 | 'ARCL', 'ASSIGN', 'ASTO', 'CF', 'CLP', 'CLV', 'DIM', 'DSE', 'EDITN', 'ENG', 397 | 'FC?', 'FC?C', 'FIX', 'FS?', 'FS?C', 'GTO', 'INDEX', 'INPUT', 'INTEG', 398 | 'ISG', 'KEY', 'LBL', 'MVAR', 'PGMINT', 'PGMSLV', 'PRV', 'RCL', 'RCL+', 399 | 'RCL-', 'RCL÷', 'RCL×', 'SCI', 'SF', 'SIZE', 'SOLVE', 'STO', 'STO+', 400 | 'STO-', 'STO÷', 'STO×', 'TONE', 'VARMENU', 'VIEW', 'X<>', 'XEQ', 'ΣREG', 401 | '|-' ] 402 | 403 | # all string ops are of the form 404 | # {length} {instruction} {string} 405 | # length is an Fx, where x is a hex digit from 1-14 406 | # the instruction is defined below 407 | # the string is encoded in hex 408 | strops = { 409 | '├': '7F', 410 | 'CLP': 'F0', 411 | 'MVAR': '90', 412 | 'CLV': 'B0', 413 | 'DIM': 'C4', 414 | 'EDITN': 'C6', 415 | 'INDEX': '87', 416 | 'ISG': '96', 417 | 'DSE': '97', 418 | 'ARCL': 'B3', 419 | 'ASTO': 'B2', 420 | # 'GTO': '1D', # fix 421 | # 'LBL': 'C0 00 Fn 00', # fix 422 | 'STO': '81', 423 | 'STO+': '82', 424 | 'STO-': '83', 425 | 'STO×': '84', 426 | 'STO÷': '85', 427 | 'RCL': '91', 428 | 'RCL+': '92', 429 | 'RCL-': '93', 430 | 'RCL×': '94', 431 | 'RCL÷': '95', 432 | 'X<>': '86', 433 | 'INPUT': 'C5', 434 | 'VIEW': '80', 435 | 'PRV': 'B1', 436 | 'VARMENU': 'C1', 437 | 'PGMINT': 'B4', 438 | 'PGMSLV': 'B5', 439 | 'INTEG': 'B6', 440 | 'SOLVE': 'B7' 441 | } 442 | stropsf = {} 443 | for i in strops: 444 | stropsf[i] = '{length} ' + strops[i] + ' {string}' 445 | 446 | regops = { 447 | 'ASTO': '9A', 448 | 'ARCL': '9B', 449 | 'STO': '91', 450 | 'STO+': '92', 451 | 'STO-': '93', 452 | 'STO×': '94', 453 | 'STO÷': '95', 454 | 'RCL': '90', 455 | 'RCL+': 'F2 D1', 456 | 'RCL-': 'F2 D2', 457 | 'RCL×': 'F2 D3', 458 | 'RCL÷': 'F2 D4', 459 | 'ISG': '96', 460 | 'DSE': '97', 461 | 'X<>': 'CE', 462 | 'INPUT': 'F2 D0', 463 | 'VIEW': '98', 464 | 'SF': 'A8', 465 | 'CF': 'A9', 466 | 'FS?C': 'AA', 467 | 'FC?C': 'AB', 468 | 'FS?': 'AC', 469 | 'FC?': 'AD', 470 | 'TONE': '9F', 471 | 'ΣREG': '99', 472 | 'SIZE': 'F3 F7' 473 | } 474 | regopsf = {} 475 | for i in regops: 476 | regopsf[i] = regops[i] + ' {loc}' 477 | 478 | # shortregops = {'RCL': '2', 'STO': '3'} 479 | shortregops = {'RCL': '2{loc}', 'STO': '3{loc}'} 480 | 481 | # FIX, SCI, ENG 10 or 11 482 | # All use 'F1' command and then D5-7 or E5-7 483 | # 10 = D 484 | # 11 = E 485 | # FIX = 5 486 | # SCI = 6 487 | # ENG = 7 488 | # I'm putting this all in a dict so it is programmatically accessible 489 | dispops = { 490 | 'FIX': '{:1x}'.format(5), 491 | 'SCI': '{:1x}'.format(6), 492 | 'ENG': '{:1x}'.format(7), 493 | 'op': 'F1 {loc}', 494 | 10: '{:1x}'.format(13), 495 | 11: '{:1x}'.format(14) 496 | } 497 | 498 | indregops0 = { 499 | 'FIX': '9C', 500 | 'SCI': '9D', 501 | 'ENG': '9E', 502 | 'DIM': 'F2 EC', 503 | 'EDITN': 'F2 EF', 504 | 'INDEX': 'F2 DA', 505 | 'INPUT': 'F2 EE', 506 | 'CLV': 'F2 D8', 507 | 'VARMENU': 'F2 F8', 508 | 'PRV': 'F2 D9', 509 | 'PGMINT': 'F2 E8', 510 | 'PGMSLV': 'F2 E9', 511 | 'INTEG': 'F2 EA', 512 | 'SOLVE': 'F2 EB', 513 | } 514 | indregops = regops.copy() 515 | indregops.update(indregops0) 516 | 517 | indregopsf = {} 518 | for i in indregops: 519 | indregopsf[i] = indregops[i] + ' {loc}' 520 | 521 | indstrops = { 522 | 'STO': '89', 523 | 'STO+': '8A', 524 | 'STO-': '8B', 525 | 'STO×': '8C', 526 | 'STO÷': '8D', 527 | 'RCL': '99', 528 | 'RCL+': '9A', 529 | 'RCL-': '9B', 530 | 'RCL×': '9C', 531 | 'RCL÷': '9D', 532 | 'GTO': 'AE', 533 | 'XEQ': 'AF', 534 | 'ASTO': 'BA', 535 | 'ARCL': 'BB', 536 | 'FIX': 'DC', 537 | 'SCI': 'DD', 538 | 'ENG': 'DE', 539 | 'ISG': '9E', 540 | 'DSE': '9F', 541 | 'DIM': 'CC', 542 | 'EDITN': 'CE', 543 | 'INDEX': '8F', 544 | 'X<>': '8E', 545 | 'SF': 'A8', 546 | 'CF': 'A9', 547 | 'FS?C': 'AA', 548 | 'FC?C': 'AB', 549 | 'FS?': 'AC', 550 | 'FC?': 'AD', 551 | 'VIEW': '88', 552 | 'INPUT': 'CD', 553 | 'CLV': 'B8', 554 | 'TONE': 'DF', 555 | 'VARMENU': 'C9', 556 | 'PRV': 'B9', 557 | 'ΣREG': 'DB', 558 | 'PGMINT': 'BC', 559 | 'PGMSLV': 'BD', 560 | 'INTEG': 'BE', 561 | 'SOLVE': 'BF', 562 | } 563 | indstropsf = {} 564 | for i in indstrops: 565 | indstropsf[i] = '{length} ' + indstrops[i] + ' {string}' 566 | 567 | lblops = { 568 | 'LBL': 'CF {loc}', 569 | 'GTO': 'D0 00 {loc}', 570 | 'XEQ': 'E0 00 {loc}', 571 | 'LBLstr': 'C0 00 {length} 00 {string}', 572 | 'GTOstr': '1D {length} {string}', 573 | 'XEQstr': '1E {length} {string}', 574 | 'GTOXEQind': 'AE {loc}', 575 | 'GTOind': 0, 576 | 'XEQind': 128, 577 | 'GTOindST': 112, 578 | 'XEQindST': 240, 579 | 'GTOindstr': '{length} AE {string}', 580 | 'XEQindstr': '{length} AF {string}' 581 | } 582 | 583 | shortlblops = { 584 | 'LBL': 1, # '0' 585 | 'GTO': 177, # 'B1' 586 | } 587 | 588 | shortlblops = { 589 | 'LBL': '{loc}', # '0' 590 | 'GTO': '{loc} 00', # 'B1' 591 | 'LBLv': 1, # '0' 592 | 'GTOv': 177, # 'B1' 593 | } 594 | 595 | toops = { 596 | 'ASSIGN': 'C0', 597 | 'KEY': 'F3', 598 | 'KEYXEQ': 'E2', 599 | 'KEYGTO': 'E3', 600 | 'KEYXEQstr': 'C2', 601 | 'KEYGTOstr': 'C3', 602 | 'KEYXEQindstr': 'CA', 603 | 'KEYGTOindstr': 'CB' 604 | } 605 | 606 | otherops= { 607 | 'ASSIGN': '{length} C0 {string} {loc}', 608 | } 609 | 610 | keyops = { 611 | 'KEYXEQ': 'E2', 612 | 'KEYGTO': 'E3', 613 | 'KEYXEQstr': 'C2', 614 | 'KEYGTOstr': 'C3', 615 | 'KEYXEQindstr': 'CA', 616 | 'KEYGTOindstr': 'CB' 617 | } 618 | keyopsf = {} 619 | for i in keyops: 620 | keyopsf[i] = '{length} ' + keyops[i] + ' {key} {loc}' 621 | 622 | 623 | # command translations 624 | commandtr = { 625 | # 41 commands 626 | 'ST+' : 'STO+', 627 | 'ST-' : 'STO-', 628 | 'STx' : 'STOx', 629 | 'ST÷' : 'STO÷', 630 | # prettify 631 | "R^": "R↑", 632 | r'R\v': "R↓", 633 | "Rv": "R↓", 634 | "RDN": "R↓", 635 | "\GS+": "Σ+", 636 | "\GS-": "Σ-", 637 | "CL\GS": "CLΣ", 638 | "\GSREG": "PRΣ", 639 | "\GSREG?": "ΣREG", 640 | "SUM+": "Σ+", 641 | "SUM-": "Σ-", 642 | "CLSUM": "CLΣ", 643 | "SUMREG": "PRΣ", 644 | "SUMREG?": "ΣREG", 645 | "X#0?": "X≠0?" , 646 | "X<=0?": "X≤0?" , 647 | "X>=0?": "X≥0?" , 648 | "X#Y?": "X≠Y?" , 649 | "X<=Y?": "X≤Y?" , 650 | "X>=Y?": "X≥Y?" , 651 | '|-': '├', 652 | "->POL": "→POL", 653 | "->REC": "→REC", 654 | "->DEG": "→DEG", 655 | "->RAD": "→RAD", 656 | "->OCT": "→OCT", 657 | "->DEC": "→DEC", 658 | "->HMS": "→HMS", 659 | "->HR": "→HR", 660 | "\->POL": "→POL", 661 | "\->REC": "→REC", 662 | "\->DEG": "→DEG", 663 | "\->RAD": "→RAD", 664 | "\->OCT": "→OCT", 665 | "\->DEC": "→DEC", 666 | "\->HMS": "→HMS", 667 | "\->HR": "→HR", 668 | "+/-": "±", 669 | "CHS": "±", 670 | "FACT": "N!", 671 | # "x": "×", 672 | # "*": "×", 673 | # "/": "÷", 674 | "RCLx": "RCL×", 675 | "RCL*": "RCL×", 676 | "RCL/": "RCL÷", 677 | "STOx": "STO×", 678 | "STO*": "STO×", 679 | "STO/": "STO÷", 680 | 681 | "10^X": "10↑X", 682 | "X^2": "X↑2", 683 | "Y^X": "Y↑X", 684 | "BASEx": "BASE×", 685 | "BASE+/-": "BASE±", 686 | "E^X-1": "E↑X-1", 687 | "ENTER^": "ENTER" 688 | } 689 | 690 | # escaped character sequences 691 | chartr = { 692 | r'\:-': "÷", 693 | r'\x': "×", 694 | r'\v/': "√", 695 | r'\S': "∫", 696 | r'\FUZ': "▒", 697 | r'\GS': "Σ", 698 | r'\|>': "▸", 699 | r'\PI': "π", 700 | r'\?': "¿", 701 | r'\<=': "≤", 702 | # r'[LF]': "line feed", 703 | # r'[LF]': "␊", # we should really use this character but the SM encoder 704 | # does not so we won't 705 | r'\>=': "≥", 706 | r'\#': "≠", 707 | r'\': "→", 710 | r'\<-': "←", 711 | r'\PND': "£", 712 | r'\m': "μ", 713 | r'\o': "°", 714 | r'\Ao': "Å", 715 | r'\N~': "Ñ", 716 | r'\A"': "Ä", 717 | r'\<\\': "∡", 718 | r'\E': "ᴇ", 719 | r'\AE': "Æ", 720 | r'\...': "…", 721 | r'\O"': "Ö", 722 | r'\U"': "Ü", 723 | r'^': "↑", 724 | r'\^': "↑", 725 | r'\.': "•" 726 | } 727 | 728 | # regexes 729 | bytere = re.compile(r'\d*\s*{\s*\d+-Byte Prgm\s}', re.I) 730 | linenore = re.compile(r'^[0-9]*▸?\s*') 731 | regre = re.compile(r'\d\d') 732 | locallbl = list('ABCDEFGHIJabcde') 733 | labelre = re.compile(r'^([0-9]*)\s(LBL)') 734 | 735 | numberre = re.compile(r'-?\d+(\.\d+|)((ᴇ|e|E)-?\d+|)') 736 | strre = re.compile('\".*\"') 737 | concatre = re.compile('^\|-"') 738 | 739 | ndigitre = re.compile(r'(\d)') 740 | ndecimalre = re.compile(r'\.') 741 | nere = re.compile(r'(ᴇ|e|E)') 742 | nminusre = re.compile(r'-') 743 | 744 | def locallblhex(lbl): 745 | """Converts local labels (A-J, a-e) to hex.""" 746 | if lbl.isupper(): 747 | loc = '{:02x}'.format(ord(lbl) + 37) 748 | else: 749 | loc = '{:02x}'.format(ord(lbl) + 26) 750 | return loc 751 | 752 | def numtoraw(number): 753 | """Converts numbers to FOCAL digits.""" 754 | # 16 + digit 755 | n = ndigitre.sub(r' 1\1', number) 756 | n = n.replace('.', ' 1A') 757 | n = nere.sub(r' 1B', n) 758 | n = n.replace('-', ' 1C').strip() 759 | n = n + ' 00' 760 | return n 761 | 762 | def strtoraw(st, extra = 0): 763 | """Converts a string to FOCAL hex. 764 | 765 | Returns a dict with the original string, encoded string, encoded length, 766 | and the number of extra characters to be added to the length 767 | calculation.""" 768 | l = len(st) 769 | if l > 16: 770 | st = st[:15] 771 | st2 = st.replace('[LF]', "␊") 772 | length = '{:02x}'.format(240 + len(st2) + extra) 773 | encst = ''.join(['{:02x}'.format(chars[i]) for i in st2]) 774 | d = {'length': length.upper(), 775 | 'string': encst, 776 | 'original': st, 777 | 'extra': extra} 778 | return d 779 | 780 | def remove_lineno(lines): 781 | """Remove line numbers for input files.""" 782 | if bytere.match(lines[0]): 783 | lines = lines[1:] 784 | if type(lines) == type(''): 785 | lines = lines.split("\n") 786 | return [linenore.sub('', line.strip()) for line in lines] 787 | 788 | def translatecmds(lines): 789 | """Translate commands to unified command set.""" 790 | out = [] 791 | for line in lines: 792 | words = line.split(' ') 793 | if words[0] in commandtr: 794 | tmp = commandtr[words[0]] + ' ' + ' '.join(words[1:]) 795 | # a few commands don't have extra words, so just strip out the 796 | # spaces added from the above line 797 | out.append(tmp.strip()) 798 | elif concatre.match(line): 799 | out.append(concatre.sub('├"', line)) 800 | else: 801 | out.append(line) 802 | return out 803 | 804 | chartrre = re.compile('({})'.format('|'.join(map(re.escape, chartr.keys())))) 805 | 806 | def translatechars(lines): 807 | """Translate escaped characters to unified character set.""" 808 | tlines = [] 809 | for l in lines: 810 | tlines.append(chartrre.sub(lambda m: chartr[m.group()], l)) 811 | return tlines 812 | 813 | def tohex(lines): 814 | """Convert the program listing.""" 815 | out = [] 816 | for lineno, line in enumerate(lines): 817 | d = {} 818 | if line in fixed.keys(): 819 | out.append(fixed[line]) 820 | elif strre.match(line): 821 | w = line.strip('"') 822 | d = strtoraw(w) 823 | cmd = "{length} {string}".format(**d) 824 | out.append(cmd) 825 | elif numberre.match(line): 826 | out.append(numtoraw(line)) 827 | elif line[0] == "├": 828 | # concatenation string 829 | w = line[1:].strip('"') 830 | cmd = stropsf[line[0]] 831 | d = strtoraw(w, 1) 832 | out.append(cmd.format(**d)) 833 | else: 834 | outwords = [] 835 | words = line.split(" ") 836 | if words[0] in modops: 837 | if words[0] == 'ASSIGN': 838 | w = words[1].strip('"') 839 | d = strtoraw(w, 2) 840 | d['loc'] = '{:02x}'.format(int(words[3]) - 1) 841 | cmd = otherops[words[0]] 842 | out.append(cmd.format(**d)) 843 | if words[0] == 'KEY': 844 | reg = words[3] 845 | d['length'] = 'F3' 846 | if strre.match(reg): 847 | w = reg.strip('"') 848 | d = strtoraw(w, 2) 849 | d['loc'] = d['string'] 850 | cmd = keyopsf[words[0]+words[2]+'str'] 851 | elif regre.match(reg): 852 | d['loc'] = '{:02x}'.format(int(reg)) 853 | cmd = keyopsf[words[0]+words[2]] 854 | elif reg in locallbl: 855 | d['loc'] = locallblhex(reg) 856 | cmd = keyopsf[words[0]+words[2]] 857 | elif reg == 'IND': 858 | if words[4] == 'ST': 859 | cmd = keyopsf[words[0]+words[2]] 860 | d['loc'] = 'F{}'.format(stacks[words[5]]) 861 | elif regre.match(words[4]): 862 | cmd = keyopsf[words[0]+words[2]] 863 | d['loc'] = '{:02x}'.format(128 + int(words[4])) 864 | elif strre.match(words[4]): 865 | w = words[4].strip('"') 866 | d = strtoraw(w, 2) 867 | d['loc'] = d['string'] 868 | cmd = keyopsf[words[0]+words[2]+'indstr'] 869 | d['key'] = '{:02x}'.format(int(words[1])) 870 | out.append(cmd.format(**d)) 871 | # first look for commands with a string as the argument 872 | elif words[0] in stropsf and strre.match(words[1]): 873 | w = words[1].strip('"') 874 | cmd = stropsf[words[0]] 875 | d = strtoraw(w, 1) 876 | out.append(cmd.format(**d)) 877 | # look for numerical register operations 878 | elif words[0] in regops and regre.match(words[1]): 879 | num = int(words[1]) 880 | d['loc'] = '{:02x}'.format(num) 881 | cmd = regopsf[words[0]] 882 | if words[0] == 'SIZE': 883 | d['loc'] = "{:02x}{:02x}".format(int(num/256), 884 | int(num % 256)) 885 | elif num < 16 and words[0] in ['STO', 'RCL']: 886 | cmd = shortregops[words[0]] 887 | d['loc'] = '{:1x}'.format(num) 888 | out.append(cmd.format(**d)) 889 | # look for stack register operations 890 | elif words[0] in regops and words[1] == 'ST': 891 | cmd = regopsf[words[0]] 892 | d['loc'] = '7{}'.format(stacks[words[2]]) 893 | out.append(cmd.format(**d)) 894 | # stack register operations without ST 895 | elif words[0] in regops and words[1] in stacks: 896 | cmd = regopsf[words[0]] 897 | d['loc'] = '7{}'.format(stacks[words[1]]) 898 | out.append(cmd.format(**d)) 899 | # look for IND indirect register operations 900 | elif words[0] in indregops and words[1] == 'IND': 901 | cmd = indregopsf[words[0]] 902 | # look for a register number 903 | if regre.match(words[2]): 904 | num = int(words[2]) 905 | d['loc'] = '{:02x}'.format(num + 128) 906 | # look for a stack 907 | elif words[2] == 'ST': 908 | d['loc'] = 'F{}'.format(stacks[words[3]]) 909 | # look for a string 910 | elif strre.match(words[2]): 911 | w = words[2].strip('"') 912 | cmd = indstropsf[words[0]] 913 | d = strtoraw(w, 1) 914 | out.append(cmd.format(**d)) 915 | # labels ops - GTO, XEQ, LBL, KEY 916 | elif words[0] in lblops: 917 | if regre.match(words[1]): 918 | num = int(words[1]) 919 | # local short labels - < 16 920 | if num < 15 and words[0] in shortlblops: 921 | num2 = shortlblops[words[0]+'v'] 922 | d['loc'] = '{:02x}'.format(num2 + num) 923 | cmd = shortlblops[words[0]] 924 | # local 'long' labels - > 15 925 | else: 926 | cmd = lblops[words[0]] 927 | d['loc'] = '{:02x}'.format(num) 928 | # local letter labels 929 | # I don't like this but I'm not sure what the math was for 930 | # local letter labels... 931 | elif words[1] in locallbl: 932 | d['loc'] = locallblhex(words[1]) 933 | cmd = lblops[words[0]] 934 | # string labels 935 | elif strre.match(words[1]): 936 | w = words[1].strip('"') 937 | cmd = lblops[words[0]+'str'] 938 | if words[0] == 'LBL': 939 | d = strtoraw(w, 1) 940 | else: 941 | d = strtoraw(w) 942 | # INDirect labels 943 | elif words[1] == 'IND': 944 | # string labels 945 | cmd = lblops['GTOXEQind'] 946 | if words[2] == 'ST': 947 | num = lblops[words[0] + 'indST'] + stacks[words[3]] 948 | d['loc'] = '{:02x}'.format(num) 949 | elif regre.match(words[2]): 950 | num = lblops[words[0] + 'ind'] + int(words[2]) 951 | d['loc'] = '{:02x}'.format(num) 952 | elif strre.match(words[2]): 953 | w = words[2].strip('"') 954 | d = strtoraw(w, 1) 955 | cmd = lblops[words[0]+'indstr'] 956 | out.append(cmd.format(**d)) 957 | elif words[0] in ['FIX', 'SCI', 'ENG', 'TONE']: 958 | if int(words[1]) < 10: 959 | cmd = indregopsf[words[0]] 960 | d['loc'] = '{:02x}'.format(int(words[1])) 961 | elif int(words[1]) in [10, 11]: 962 | cmd = dispops['op'] 963 | d['loc'] = dispops[int(words[1])] + dispops[words[0]] 964 | out.append(cmd.format(**d)) 965 | else: 966 | print("error - line no. {}: {}".format(lineno + 1, 967 | ' '.join(words))) 968 | return out 969 | 970 | def prettify_file(lines, numbytes): 971 | """'Prettifies' a program listing with line numbers and a byte count.""" 972 | totallines = len(lines) 973 | numdigits = len(str(totallines)) 974 | if numdigits < 2: 975 | numdigits = 2 976 | progstr = "{{ {:d}-Byte Prgm }}".format(numbytes) 977 | newlines = lines.copy() 978 | newlines.insert(0, progstr) 979 | fmtstr = "{{:0{}d}} ".format(numdigits) 980 | numlines = [] 981 | for n,l in enumerate(newlines): 982 | tmpline = fmtstr.format(n) + l 983 | tmpline = labelre.sub(r'\1▸\2', tmpline) 984 | numlines.append(tmpline) 985 | return numlines 986 | 987 | def write_raw(ofn, hexout): 988 | """Write an encoded program to a file.""" 989 | with open(ofn, 'wb') as outf: 990 | outf.write(binascii.unhexlify(hexout)) 991 | 992 | def read_file(fn): 993 | """Read a program from a file or standard in.""" 994 | lines = [l for l in fileinput.input(fn)] 995 | lines = ''.join(lines).strip() 996 | lines = lines.split('\n') 997 | return lines 998 | 999 | def main(argv=None): 1000 | """Main program loop.""" 1001 | 1002 | if argv is None: 1003 | argv = sys.argv 1004 | programName = os.path.basename(argv[0]) 1005 | 1006 | description ="""Encodes programs for the DM42 calculator. 1007 | 1008 | The calculator uses a superset of HP42S program commands. By default, if 1009 | the encoded program is writen to a file with the [-w] option, the raw file is 1010 | written to the same location as the input file.""" 1011 | parser = argparse.ArgumentParser(description = description) 1012 | parser.add_argument('--version', action='version', 1013 | version='%(prog)s {}'.format(__version__)) 1014 | 1015 | group1 = parser.add_argument_group('printing', 1016 | 'commands that print the processed program.') 1017 | group1.add_argument('-p', '--print', action="store_true", dest="print", 1018 | help="Prints the file - prettified output", default=False) 1019 | 1020 | group1.add_argument('--hex', action="store_true", 1021 | dest="hex", help="Prints encoded hex", default=False) 1022 | 1023 | group1.add_argument('-l', '--hexlines', action="store_true", 1024 | dest="hexlines", 1025 | help="Prints encoded hex one command per line", default=False) 1026 | 1027 | group1.add_argument('-b', '--binary', action="store_true", 1028 | dest="binary", 1029 | help="Prints encoded hex in binary - suitable for piping", 1030 | default=False) 1031 | 1032 | 1033 | group2 = parser.add_argument_group('writing', 1034 | 'commands that write the raw output.') 1035 | group2.add_argument('-o', '--out', dest="outfile", 1036 | help="Set the output raw filename") 1037 | 1038 | group2.add_argument('-w', '--write', dest="write", action='store_true', 1039 | help="Write the output raw data to a file") 1040 | 1041 | parser.add_argument('infile', help="input file name or '-' for stdin pipe") 1042 | # parser.add_argument('outfile', nargs='?') 1043 | args = parser.parse_args() 1044 | 1045 | lines = read_file(args.infile) 1046 | 1047 | # set file names 1048 | fn1, fn2 = os.path.split(args.infile) 1049 | fn2a, fn2ext = os.path.splitext(fn2) 1050 | 1051 | # remove line numbers 1052 | nonumlines = remove_lineno(lines) 1053 | # translate inconsistent/HP41 commands 1054 | lines2 = translatecmds(nonumlines) 1055 | # translate special characters 1056 | glines = translatechars(lines2) 1057 | 1058 | # convert to hex values 1059 | hexout = tohex(glines) 1060 | # remove spaces in hex codes 1061 | hexout2 = [h.replace(' ', '').upper() for h in hexout] 1062 | # remove line breaks 1063 | comphex = ''.join(hexout2) 1064 | numbytes = int(len(comphex)/2 - 3) 1065 | 1066 | if args.outfile: 1067 | args.write = True 1068 | 1069 | # prettify output 1070 | plines = prettify_file(glines, numbytes) 1071 | if args.hexlines: 1072 | print('\n'.join(hexout2)) 1073 | elif args.hex: 1074 | print(comphex) 1075 | elif args.binary: 1076 | sys.stdout.buffer.write(binascii.unhexlify(comphex)) 1077 | elif args.write: 1078 | ofn = os.path.join(fn1, fn2a + '.raw') 1079 | if not args.outfile: 1080 | if args.infile == '-': 1081 | ofn = 'out.raw' 1082 | else: 1083 | ofn = os.path.join(fn1, fn2a + '.raw') 1084 | if os.path.exists(ofn): 1085 | a, b = os.path.splitext(ofn) 1086 | ofn = a + "-{}.raw" 1087 | i = 1 1088 | while os.path.exists(ofn.format(i)): 1089 | i += 1 1090 | ofn = ofn.format(i) 1091 | else: 1092 | ofn = os.path.expanduser(args.outfile) 1093 | write_raw(ofn, comphex) 1094 | else: 1095 | print('\n'.join(plines)) 1096 | 1097 | 1098 | if __name__ == "__main__": 1099 | sys.exit(main()) 1100 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # dm42coder.py 2 | 3 | Encoder for [Swiss Micros][sm] [DM42][] calculator programs. 4 | 5 | This encoder should work for [Free42][] as well, though I have not yet included any of the mobile-only functions. There is little real need to use an encoder for [Free42][] since it will import programs directly from the text listings. Hopefully one day the [DM42][] will gain this feature from [Free42][] making this program pointless. 6 | 7 | [sm]: https://www.swissmicros.com/ 8 | [DM42]: https://www.swissmicros.com/dm42.php 9 | [Free42]: http://thomasokken.com/free42/ 10 | 11 | ## Requirements 12 | 13 | `dm42coder.py` is written in Python. It was tested using Python 3.6.5 on MacOS. The Python installation was from [Anaconda][], though any Python 3.6 or higher should work. It will likely work on lower versions of Python as well. 14 | 15 | Three modules from the standard library are used that might have issues in earlier versions of Python: *binascii*, *argparse*, and *fileinput*. *argparse* is similar to the old *optparse* and should not be too difficult to backport to Python 2 if interested. *fileinput* is only used in reading the input files or stdin, and could easily be replaced. *binascii* is only used in writing the converted hex values to binary and could be replaced by the appropriate Python 2 method. 16 | 17 | [Anaconda]: https://www.anaconda.com/download/ 18 | 19 | ## Usage 20 | 21 | The program is designed to be used from the command line. Program files to be converted can either be piped to it or specified as an argument. Output can be either written directly to a file (with automatic naming or a specified name), or can be printed to stdout as a 'normalized' text listing, a hex line-by-line listing, a fully compressed hex listing, or a binary stream that can be piped directly to a file. 22 | 23 | ### Specifying input 24 | 25 | `dm42coder.py` accepts input either by specifying a file name or piping a file to the script. To use stdin, a `-` must be used for the input filename. 26 | 27 | dm42coder.py program.txt 28 | cat program.txt | dm42coder.py - 29 | 30 | ### Printing program files 31 | 32 | With no options specified, a prettified version of the input code is printed to stdout. Escaped characters are translated, command names are normalized, line numbers are added, and a byte count is inserted at the beginning of the file. The output can also be explicitly specified with the `-p` option. 33 | 34 | dm42coder.py program.txt 35 | dm42coder.py program.txt -p 36 | 37 | To print a line-by-line listing of the converted hex values, use the `-l` option. The `--hex` option prints the same output with all whitespace removed, i.e. in one compact line. Lastly, the `-b` option can be used to print a binary stream to stdout that is suitable to be piped to another file. 38 | 39 | dm42coder.py program.txt -b | out.raw 40 | 41 | 42 | ### Writing raw files 43 | 44 | To write to a file with the name `program.raw` in the same directory as the input file: 45 | 46 | dm42coder.py program.txt -w 47 | 48 | To write to a file with a name and location of your choosing: 49 | 50 | dm42coder.py program.txt -o ~/output.raw 51 | 52 | If the input is from stdin and no file name is specified, the output file will be named `out.raw` in the directly in which the command was run. 53 | 54 | For the files created without the `-o` option (no specified name), the script will not overwrite existing files, instead adding a *-x* to the name where *x* is an incrementing number. 55 | 56 | ## TODO 57 | 58 | I'd like to add decoding functionality as well, but have not started working on that. 59 | 60 | ## Disclaimer 61 | 62 | The code has been tested on a few DM42 programs of mine. I've also checked it with the [`stress`][stress] program from [Free42][]. 63 | 64 | [stress]: http://thomasokken.com/free42/42progs/stress.txt 65 | --------------------------------------------------------------------------------