Canola
0.8.D001
|
00001 // 00002 // canola - canon canola 1614p emulator 00003 // Copyright (C) 2011, 2012 Peter Miller 00004 // 00005 // This program is free software; you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License, version 3, as 00007 // published by the Free Software Foundation. 00008 // 00009 // This program is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 // General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License along 00015 // with this program. If not, see <http://www.gnu.org/licenses/>. 00016 // 00017 00018 #include <lib/ac/math.h> 00019 #include <lib/ac/stdio.h> 00020 #include <lib/ac/string.h> 00021 #include <libexplain/output.h> 00022 00023 #include <lib/calculator.h> 00024 #include <lib/interpose/learn.h> 00025 #include <lib/interpose/ope.h> 00026 #include <lib/interpose/ope_che.h> 00027 #include <lib/interpose/pro_che.h> 00028 #include <lib/interpose/stateful.h> 00029 00030 00031 calculator::~calculator() 00032 { 00033 } 00034 00035 00036 calculator::calculator() : 00037 mstate(mstate_reset), 00038 x_register(), 00039 y_register(), 00040 program_selector(program_selector_i_ii), 00041 address(0), 00042 program_mode(program_mode_operation), 00043 deferred_action(deferred_action_none), 00044 clock_tick_mode(clock_tick_mode_disabled), 00045 printer_delay_active(false), 00046 accumulate_mode(false), 00047 constant_mode(false), 00048 constant_mode_action(deferred_action_none), 00049 strict_memory_number_checking(true), 00050 number_of_keys_since_ej(0) 00051 { 00052 interpose_by_program_mode[program_mode_learn] = 00053 interpose_stateful::create(interpose_learn::create(this)); 00054 interpose_by_program_mode[program_mode_operation] = 00055 interpose_stateful::create(interpose_ope::create(this), true); 00056 interpose_by_program_mode[program_mode_program_check] = 00057 interpose_stateful::create(interpose_pro_che::create(this)); 00058 interpose_by_program_mode[program_mode_operation_check] = 00059 interpose_stateful::create(interpose_ope_che::create(this), true); 00060 00061 memset(programme, 0, sizeof(programme)); 00062 } 00063 00064 00065 const char * 00066 calculator::mstate_name(mstate_t ms) 00067 { 00068 switch (ms) 00069 { 00070 case mstate_reset: 00071 return "mstate_reset"; 00072 00073 case mstate_lhs_digits_before_dot: 00074 return "mstate_lhs_digits_before_dot"; 00075 00076 case mstate_lhs_digits_after_dot: 00077 return "mstate_lhs_digits_after_dot"; 00078 00079 case mstate_lhs_complete: 00080 return "mstate_lhs_complete"; 00081 00082 case mstate_mul: 00083 return "mstate_mul"; 00084 00085 case mstate_mul_digits_before_dot: 00086 return "mstate_mul_digits_before_dot"; 00087 00088 case mstate_mul_digits_after_dot: 00089 return "mstate_mul_digits_after_dot"; 00090 00091 case mstate_mul_complete: 00092 return "mstate_mul_complete"; 00093 00094 default: 00095 return "???"; 00096 } 00097 } 00098 00099 00100 const char * 00101 calculator::program_mode_name(program_mode_t os) 00102 { 00103 switch (os) 00104 { 00105 case program_mode_learn: 00106 return "LRN"; 00107 00108 case program_mode_operation: 00109 return "OPE"; 00110 00111 case program_mode_program_check: 00112 return "PRO CHE"; 00113 00114 case program_mode_operation_check: 00115 return "OPE CHE"; 00116 00117 default: 00118 return "unknown"; 00119 } 00120 } 00121 00122 00123 calculator::program_selector_t 00124 calculator::program_selector_get(void) 00125 const 00126 { 00127 return program_selector; 00128 } 00129 00130 00131 void 00132 calculator::program_selector_set(program_selector_t ps) 00133 { 00134 program_selector = ps; 00135 switch (program_selector) 00136 { 00137 default: 00138 case program_selector_i_ii: 00139 break; 00140 00141 case program_selector_i: 00142 // <question> 00143 // In LRN mode, with the Program Selector Switch is set to "I+II", 00144 // if the display address is more than 121, what happends if the 00145 // Program Selector Switch is set to "I"? Does the address change? 00146 // If so, to what? 00147 // </question> 00148 if (address > 120) 00149 address = 0; 00150 break; 00151 00152 case program_selector_ii: 00153 // <question> 00154 // In LRN mode, with the Program Selector Switch is set to "I+II", 00155 // if the display address is less than 120, what happends if the 00156 // Program Selector Switch is set to "II"? Does the address change? 00157 // If so, to what? 00158 // </question> 00159 if (address < 121) 00160 address = 0; 00161 break; 00162 } 00163 update_display(); 00164 } 00165 00166 00167 void 00168 calculator::on_error(const char *fmt, ...) 00169 { 00170 va_list ap; 00171 va_start(ap, fmt); 00172 on_error_v(fmt, ap); 00173 va_end(ap); 00174 } 00175 00176 00177 void 00178 calculator::program_mode_set(program_mode_t pm) 00179 { 00180 if (program_mode == pm) 00181 return; 00182 program_mode = pm; 00183 switch (program_mode) 00184 { 00185 case program_mode_learn: 00186 break; 00187 00188 case program_mode_operation: 00189 // See the Instruction Manual, p. 45 00190 address = 0; 00191 x_register = 0; 00192 y_register = 0; 00193 mstate = mstate_reset; 00194 break; 00195 00196 case program_mode_program_check: 00197 break; 00198 00199 case program_mode_operation_check: 00200 address = 0; 00201 x_register = 0; 00202 y_register = 0; 00203 mstate = mstate_reset; 00204 break; 00205 00206 default: 00207 assert(!"this is strange"); 00208 break; 00209 } 00210 update_display(); 00211 } 00212 00213 00214 calculator::program_mode_t 00215 calculator::program_mode_get(void) 00216 { 00217 return program_mode; 00218 } 00219 00220 00221 void 00222 calculator::on_opcode(opcode_t op, const location::pointer &where) 00223 { 00224 assert(interpose_by_program_mode[program_mode]); 00225 interpose_by_program_mode[program_mode]->on_opcode(op, where); 00226 update_display(); 00227 } 00228 00229 00230 unsigned 00231 calculator::wrap_address(unsigned addr) 00232 const 00233 { 00234 switch (program_selector) 00235 { 00236 case program_selector_i_ii: 00237 default: 00238 if (addr < 1 || addr > 240) 00239 return 1; 00240 break; 00241 00242 case program_selector_i: 00243 if (addr < 1 || addr > 120) 00244 return 1; 00245 break; 00246 00247 case program_selector_ii: 00248 if (addr < 121 || addr > 240) 00249 return 121; 00250 break; 00251 } 00252 return addr; 00253 } 00254 00255 00256 unsigned char 00257 calculator::next_opcode(void) 00258 { 00259 if (!address) 00260 address = wrap_address(address); 00261 // FIXME: skip NUL bytes, but avoid infinite loop when programme is empty. 00262 unsigned char op = programme[address]; 00263 address = wrap_address(address + 1); 00264 return op; 00265 } 00266 00267 00268 location::pointer 00269 calculator::next_location(void) 00270 const 00271 { 00272 int addr = address; 00273 if (!addr) 00274 addr = wrap_address(addr); 00275 return programme_location[addr]; 00276 } 00277 00278 00279 unsigned 00280 calculator::find_flag_jump(unsigned char label) 00281 { 00282 // Question: If executing in bank II, does UJ find FJ in bank I? 00283 // 00284 // The Instruction Manual seems to say this, p. 54: 00285 // 00286 // "Consequently, where two programs A and B are simultaneously 00287 // stored, care should be exercised to avoid using a receiver 00288 // (Flag for Jump) identical to that of program A stored in I 00289 // position, when makeing program B which is to be stored in II 00290 // position." 00291 // 00292 // FIXME: The physical calculator really did look for labels this 00293 // way, but it had a slow clock speed for this. We should think 00294 // about emulating that delay. 00295 // 00296 unsigned lo = 1; 00297 unsigned hi = 240; 00298 00299 // 00300 // Instruction Manual, p. 54, says: 00301 // 00302 // "A jump is accomplished to the smallest step as viewed from 00303 // the 1st step regardless of the linear sequence of steps or 00304 // specifications such as the Program Selector Switch." 00305 // 00306 // Which makes this code a bit suspect, because it DOES honor the 00307 // Program Selector Switch. 00308 // 00309 // Question: does UJ ignore the Program Select Switch when looking for FJ? 00310 // 00311 switch (program_selector) 00312 { 00313 case program_selector_i_ii: 00314 default: 00315 break; 00316 00317 case program_selector_i: 00318 hi = 120; 00319 break; 00320 00321 case program_selector_ii: 00322 lo = 121; 00323 break; 00324 } 00325 00326 // 00327 // <question> 00328 // When searching for a FJ label, how many steps can it scan per 00329 // second? The Instruction Manual says distant jumps are slower, 00330 // but how much slower? 00331 // </question> 00332 // 00333 for (unsigned n = lo; n <= hi; ++n) 00334 { 00335 if (programme[n] == opcode_fj && programme[n + 1] == label) 00336 { 00337 // Instruction Manual, p. 54, use the first flag you find. 00338 return (n + 2); 00339 } 00340 } 00341 00342 // 00343 // It does no exist. The Instruction Manual, p.53, says to treat 00344 // this as a jump to the first instruction. 00345 // 00346 // We will issue a warning, just to be friendly-like. 00347 // 00348 on_error 00349 ( 00350 "flag jump %s not found, destination address %d used by default", 00351 opcode_raw_name((opcode_t)label).c_str(), 00352 lo 00353 ); 00354 return lo; 00355 } 00356 00357 00358 unsigned 00359 calculator::find_subroutine_flag_jump(unsigned char label) 00360 { 00361 // FIXME: The physical calculator really did look for labels this 00362 // way, but it had a slow clock speed for this. We should think 00363 // about emulating that delay. 00364 unsigned lo = 1; 00365 unsigned hi = 240; 00366 switch (program_selector) 00367 { 00368 case program_selector_i_ii: 00369 default: 00370 break; 00371 00372 case program_selector_i: 00373 hi = 120; 00374 break; 00375 00376 case program_selector_ii: 00377 lo = 121; 00378 break; 00379 } 00380 for (unsigned n = lo; n <= hi; ++n) 00381 { 00382 if (programme[n] == opcode_sfj && programme[n + 1] == label) 00383 { 00384 return (n + 2); 00385 } 00386 } 00387 00388 // 00389 // It does no exist. The manual (somewhere) says to treat this as a 00390 // jump to the first instruction. 00391 // 00392 // We will issue a warning, just to be friendly-like. 00393 // 00394 on_error 00395 ( 00396 "subroutine flag jump %s not found, destination address %d used " 00397 "by default", 00398 opcode_raw_name((opcode_t)label).c_str(), 00399 lo 00400 ); 00401 return lo; 00402 } 00403 00404 00405 void 00406 calculator::clock_tick_event(void) 00407 { 00408 // 00409 // If the printer has yet to finish printing the last line it was 00410 // given to print, we can't proceed. 00411 // 00412 if (printer_delay_active) 00413 return; 00414 00415 switch (program_mode) 00416 { 00417 case program_mode_learn: 00418 // ignore 00419 clock_tick_mode = clock_tick_mode_disabled; 00420 break; 00421 00422 case program_mode_operation: 00423 switch (clock_tick_mode) 00424 { 00425 case clock_tick_mode_disabled: 00426 break; 00427 00428 case clock_tick_mode_running: 00429 execute_one_instruction(); 00430 break; 00431 00432 case clock_tick_mode_entry: 00433 case clock_tick_mode_sj: 00434 case clock_tick_mode_ej: 00435 default: 00436 break; 00437 } 00438 update_display(); 00439 break; 00440 00441 case program_mode_program_check: 00442 case program_mode_operation_check: 00443 default: 00444 clock_tick_mode = clock_tick_mode_disabled; 00445 break; 00446 } 00447 } 00448 00449 00450 bool 00451 calculator::clock_tick_expected(void) 00452 const 00453 { 00454 return (clock_tick_mode == clock_tick_mode_running); 00455 } 00456 00457 00458 void 00459 calculator::precision_and_rounding_set(const precision_and_rounding &value) 00460 { 00461 precro_front_panel = value; 00462 precro_current = value; 00463 } 00464 00465 00466 const precision_and_rounding & 00467 calculator::precision_and_rounding_get(void) 00468 const 00469 { 00470 return precro_front_panel; 00471 } 00472 00473 00474 bool 00475 calculator::printer_finished_event_expected(void) 00476 const 00477 { 00478 return printer_delay_active; 00479 } 00480 00481 00482 void 00483 calculator::printer_finished_event(void) 00484 { 00485 printer_delay_active = false; 00486 } 00487 00488 00489 bool 00490 calculator::accumulate_mode_get(void) 00491 const 00492 { 00493 return accumulate_mode; 00494 } 00495 00496 00497 void 00498 calculator::accumulate_mode_set(bool value) 00499 { 00500 accumulate_mode = value; 00501 } 00502 00503 00504 void 00505 calculator::constant_mode_set(bool value) 00506 { 00507 constant_mode = value; 00508 } 00509 00510 00511 static std::string 00512 downcase(const std::string &text) 00513 { 00514 std::string result; 00515 const char *s = text.c_str(); 00516 for (;;) 00517 { 00518 unsigned char c = *s++; 00519 if (!c) 00520 return result; 00521 if (isupper(c)) 00522 c = tolower(c); 00523 result += c; 00524 } 00525 } 00526 00527 00528 void 00529 calculator::property(const std::string &name, const std::string &value) 00530 { 00531 properties.insert(properties_t::value_type(downcase(name), value)); 00532 } 00533 00534 00535 bool 00536 calculator::strict_memory_number_checking_required(void) 00537 const 00538 { 00539 return strict_memory_number_checking; 00540 } 00541 00542 00543 void 00544 calculator::strict_memory_number_checking_set(bool value) 00545 { 00546 strict_memory_number_checking = value; 00547 } 00548 00549 00550 // vim: set ts=8 sw=4 et :