/******************************************************************** * Description: interp_o_word.cc * * * Author: Kenneth Lerman * License: GPL Version 2 * System: Linux * * Copyright 2005 All rights reserved. * * Last change: Michael Haberler 7/2011 * ********************************************************************/ #define BOOST_PYTHON_MAX_ARITY 4 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rs274ngc.hh" #include "rs274ngc_return.hh" #include "interp_return.hh" #include "interp_internal.hh" #include "rs274ngc_interp.hh" #include "python_plugin.hh" #include "interp_python.hh" #include namespace bp = boost::python; //======================================================================== // Functions for control stuff (O-words) //======================================================================== /* Given the root of a directory tree and a file name, find the path to the file, if any. */ int Interp::findFile( // ARGUMENTS char *direct, // the directory to start looking in char *target, // the name of the file to find char *foundFileDirect) // where to store the result { FILE *file; DIR *aDir; struct dirent *aFile; char targetPath[PATH_MAX+1]; snprintf(targetPath, PATH_MAX, "%s/%s", direct, target); file = fopen(targetPath, "r"); if (file) { strncpy(foundFileDirect, direct, PATH_MAX); fclose(file); return INTERP_OK; } aDir = opendir(direct); if (!aDir) { ERS(NCE_FILE_NOT_OPEN); } while ((aFile = readdir(aDir))) { if (aFile->d_type == DT_DIR && (0 != strcmp(aFile->d_name, "..")) && (0 != strcmp(aFile->d_name, "."))) { char path[PATH_MAX+1]; snprintf(path, PATH_MAX, "%s/%s", direct, aFile->d_name); if (INTERP_OK == findFile(path, target, foundFileDirect)) { closedir(aDir); return INTERP_OK; } } } closedir(aDir); ERS(NCE_FILE_NOT_OPEN); } /* * this now uses STL maps for offset access */ int Interp::control_save_offset(block_pointer block, /* pointer to a block of RS274/NGC instructions */ setup_pointer settings) /* pointer to machine settings */ { static char name[] = "control_save_offset"; offset_pointer op = NULL; logOword("Entered:%s for o_name:|%s|", name, block->o_name); if (control_find_oword(block, settings, &op) == INTERP_OK) { if (settings->sequence_number -1 == op->sequence_number) // hit this definition before; must be in illegal location ERS(_("File:%s line:%d sub: o|%s| found in illegal location"), settings->filename, settings->sequence_number, block->o_name); else // already exists ERS(_("File:%s line:%d redefining sub: o|%s| already defined in file:%s"), settings->filename, settings->sequence_number, block->o_name, op->filename); } offset new_offset; new_offset.type = block->o_type; new_offset.offset = block->offset; new_offset.filename = strstore(settings->filename); new_offset.repeat_count = -1; // the sequence number has already been bumped, so save // the proper value new_offset.sequence_number = settings->sequence_number - 1; settings->offset_map[block->o_name] = new_offset; return INTERP_OK; } int Interp::control_find_oword(block_pointer block, // pointer to block setup_pointer settings, // pointer to machine settings offset_pointer *op) // pointer to offset descriptor { static char name[] = "control_find_oword"; offset_map_iterator it; it = settings->offset_map.find(block->o_name); if (it != settings->offset_map.end()) { *op = &it->second; return INTERP_OK; } else { logOword("%s: Unknown oword name: |%s|", name, block->o_name); ERS(NCE_UNKNOWN_OWORD_NUMBER); } } const char *o_ops[] = { "O_none", "O_sub", "O_endsub", "O_call", "O_do", "O_while", "O_if", "O_elseif", "O_else", "O_endif", "O_break", "O_continue", "O_endwhile", "O_return", "O_repeat", "O_endrepeat", "M_98", "M_99", "O_", }; const char *call_statenames[] = { "CS_NORMAL", "CS_REEXEC_PROLOG", "CS_REEXEC_PYBODY", "CS_REEXEC_EPILOG", "CS_REEXEC_PYOSUB", }; const char *call_typenames[] = { "CT_NONE", "CT_NGC_OWORD_SUB", "CT_NGC_M98_SUB", "CT_PYTHON_OWORD_SUB", "CT_REMAP", }; int Interp::execute_call(setup_pointer settings, context_pointer current_frame, int call_type) { int status = INTERP_OK; int i; bp::list plist; context_pointer previous_frame = &settings->sub_context[settings->call_level-1]; block_pointer eblock = &EXECUTING_BLOCK(*settings); logOword("execute_call %s type=%s state=%s cl=%d rl=%d", current_frame->subName, call_typenames[call_type], call_statenames[settings->call_state], settings->call_level,settings->remap_level); switch (call_type) { case CT_NONE: ERS("BUG: execute_call(): arrived with call_type=CT_NONE"); break; case CT_NGC_OWORD_SUB: case CT_NGC_M98_SUB: // if we were skipping, no longer if (settings->skipping_o) { logOword("case O_call/M_98 -- no longer skipping to:|%s|", settings->skipping_o); settings->skipping_o = NULL; } // copy parameters from context // save old values of parameters if (call_type != CT_NGC_M98_SUB) // M98: pass #1..#30 from parent for (i = 0; i < INTERP_SUB_PARAMS; i++) { previous_frame->saved_params[i] = settings->parameters[i + INTERP_FIRST_SUBROUTINE_PARAM]; settings->parameters[i + INTERP_FIRST_SUBROUTINE_PARAM] = eblock->params[i]; } // Set up return to next block (may be overridden by M98) if (settings->file_pointer == NULL) // if the previous file was NULL, mark position as -1 so as not to // reopen it on return. previous_frame->position = -1; else previous_frame->position = ftell(settings->file_pointer); previous_frame->filename = strstore(settings->filename); previous_frame->sequence_number = settings->sequence_number; logOword("saving return location[cl=%d]: %s:%d offset=%ld", settings->call_level-1, previous_frame->filename, previous_frame->sequence_number, previous_frame->position); if (FEATURE(OWORD_N_ARGS)) { // let any Oword sub know the number of parameters CHP(add_named_param("n_args", PA_READONLY)); CHP(store_named_param(settings, "n_args", (double )eblock->param_cnt, OVERRIDE_READONLY)); } // M98 w/ L-word: Handle looping if (eblock->o_type == M_98 && eblock->l_flag) { // Set up loop counter // Loop count from L-word if (previous_frame->m98_loop_counter == -1) // If m98_loop_counter == -1, this is a new loop previous_frame->m98_loop_counter = round_to_int(eblock->l_number); logOword("Looping on O%s; remaining: %d", eblock->o_name, previous_frame->m98_loop_counter); // if repeats remain, set up another loop previous_frame->m98_loop_counter--; if (previous_frame->m98_loop_counter > 0) { // Set return location to repeat this block for next // iteration previous_frame->position = eblock->offset; // Unbump line number for next iteration previous_frame->sequence_number--; } else // No loops remain; reset loop counter previous_frame->m98_loop_counter = -1; } if (eblock->o_type == M_98 && eblock->l_flag && eblock->l_number == 0) logOword("M98 L0 instruction; skipping call"); else if (control_back_to(eblock, settings) == INTERP_ERROR) { settings->call_level--; ERS(NCE_UNABLE_TO_OPEN_FILE,eblock->o_name); return INTERP_ERROR; } break; case CT_PYTHON_OWORD_SUB: switch (settings->call_state) { case CS_NORMAL: settings->return_value = 0.0; settings->value_returned = 0; previous_frame->sequence_number = settings->sequence_number; previous_frame->filename = strstore(settings->filename); plist.append(*settings->pythis); // self for(int i = 0; i < eblock->param_cnt; i++) plist.append(eblock->params[i]); // positional args current_frame->pystuff.impl->tupleargs = bp::tuple(plist); current_frame->pystuff.impl->kwargs = bp::dict(); case CS_REEXEC_PYOSUB: if (settings->call_state == CS_REEXEC_PYOSUB) CHP(read_inputs(settings)); status = pycall(settings, current_frame, OWORD_MODULE, current_frame->subName, settings->call_state == CS_NORMAL ? PY_OWORDCALL : PY_FINISH_OWORDCALL); CHKS(status == INTERP_ERROR, "pycall(%s.%s) failed", OWORD_MODULE, current_frame->subName) ; switch (status = handler_returned(settings, current_frame, current_frame->subName, true)) { case INTERP_EXECUTE_FINISH: settings->call_state = CS_REEXEC_PYOSUB; break; default: settings->call_state = CS_NORMAL; settings->sequence_number = previous_frame->sequence_number; CHP(status); // M73 auto-restore is of dubious value in a Python subroutine CHP(leave_context(settings,false)); } break; } break; case CT_REMAP: block_pointer cblock = &CONTROLLING_BLOCK(*settings); remap_pointer remap = cblock->executing_remap; switch (settings->call_state) { case CS_NORMAL: if (remap->remap_py || remap->prolog_func || remap->epilog_func) { CHKS(!PYUSABLE, "%s (remapped) uses Python functions, but the Python plugin is not available", remap->name); plist.append(*settings->pythis); //self current_frame->pystuff.impl->tupleargs = bp::tuple(plist); current_frame->pystuff.impl->kwargs = bp::dict(); } if (remap->argspec && (strchr(remap->argspec, '@') == NULL)) { // add_parameters will decorate kwargs as per argspec // if named local parameters specified CHP(add_parameters(settings, cblock, NULL)); } // fall through case CS_REEXEC_PROLOG: if (remap->prolog_func) { status = pycall(settings, current_frame, REMAP_MODULE,remap->prolog_func, settings->call_state == CS_NORMAL ? PY_PROLOG : PY_FINISH_PROLOG); CHKS(status == INTERP_ERROR, "pycall(%s.%s) failed", REMAP_MODULE, remap->prolog_func); switch (status = handler_returned(settings, current_frame, current_frame->subName, false)) { case INTERP_EXECUTE_FINISH: settings->call_state = CS_REEXEC_PROLOG; return status; default: settings->call_state = CS_NORMAL; //settings->sequence_number = previous_frame->sequence_number; CHP(status); } } // fall through case CS_REEXEC_PYBODY: if (remap->remap_py) { status = pycall(settings, current_frame, REMAP_MODULE, remap->remap_py, settings->call_state == CS_NORMAL ? PY_BODY : PY_FINISH_BODY); CHP(status); switch (status = handler_returned(settings, current_frame, current_frame->subName, false)) { case INTERP_EXECUTE_FINISH: settings->call_state = CS_REEXEC_PYBODY; return status; default: settings->call_state = CS_NORMAL; settings->sequence_number = previous_frame->sequence_number; CHP(status); // epilog is not supported on python body - makes no sense CHP(leave_context(settings,false)); ERP(remap_finished(-cblock->phase)); } } // call the NGC remap procedure assert(settings->call_state == CS_NORMAL); if (remap->remap_ngc) { CHP(execute_call(settings, current_frame, CT_NGC_OWORD_SUB)); } } } return status; } // this is executed only for NGC subs, either normal ones or part of a remap // subs whose name is a Py callable are handled inline in execute_call() // since there is no corresponding O_return/O_endsub to execute. int Interp::execute_return(setup_pointer settings, context_pointer current_frame,int call_type) { int status = INTERP_OK; logOword("execute_return %s type=%s state=%s", current_frame->subName, call_typenames[call_type], call_statenames[settings->call_state]); block_pointer cblock = &CONTROLLING_BLOCK(*settings); block_pointer eblock = &EXECUTING_BLOCK(*settings); context_pointer previous_frame = settings->call_level > 0 ? &settings->sub_context[settings->call_level - 1] : nullptr; // if level is not zero, in a call // otherwise in a defn // if we were skipping, no longer if (settings->skipping_o && (eblock->o_type == O_endsub)) { logOword("case O_%s -- no longer skipping to:|%s|", (eblock->o_type == O_endsub) ? "endsub" : "return", settings->skipping_o); settings->skipping_o = NULL; } switch (call_type) { case CT_REMAP: switch (settings->call_state) { case CS_NORMAL: case CS_REEXEC_EPILOG: if (cblock->executing_remap && cblock->executing_remap->epilog_func) { if (settings->call_state == CS_REEXEC_EPILOG) CHP(read_inputs(settings)); status = pycall(settings, current_frame, REMAP_MODULE, cblock->executing_remap->epilog_func, settings->call_state == CS_NORMAL ? PY_EPILOG : PY_FINISH_EPILOG); CHP(status); switch (status = handler_returned(settings, current_frame, current_frame->subName, false)) { case INTERP_EXECUTE_FINISH: settings->call_state = CS_REEXEC_EPILOG; eblock->call_type = CT_REMAP; CHP(status); default: settings->call_state = CS_NORMAL; settings->sequence_number = previous_frame->sequence_number; CHP(status); // leave_context() is done by falling through into CT_NGC_OWORD_SUB code } } } // fall through to normal NGC return handling case CT_NGC_OWORD_SUB: case CT_NGC_M98_SUB: case CT_NONE: // sub definition if (settings->call_level != 0) { // restore subroutine parameters. if (call_type != CT_NGC_M98_SUB) // M98: pass #1..#30 from parent for(int i = 0; i < INTERP_SUB_PARAMS; i++) { settings->parameters[i+INTERP_FIRST_SUBROUTINE_PARAM] = previous_frame->saved_params[i]; } // file at this level was marked as closed, so dont reopen. if (previous_frame->position == -1) { if (settings->file_pointer) fclose(settings->file_pointer); settings->file_pointer = NULL; rtapi_strxcpy(settings->filename, ""); } else { if(settings->file_pointer == NULL) { ERS(NCE_FILE_NOT_OPEN); } //!!!KL must open the new file, if changed if (0 != strcmp(settings->filename, previous_frame->filename)) { fclose(settings->file_pointer); settings->file_pointer = fopen(previous_frame->filename, "r"); if (settings->file_pointer == NULL) { ERS(NCE_CANNOT_REOPEN_FILE, previous_frame->filename, strerror(errno)); } rtapi_strxcpy(settings->filename, previous_frame->filename); } fseek(settings->file_pointer, previous_frame->position, SEEK_SET); settings->sequence_number = previous_frame->sequence_number; logOword("endsub/return: %s:%d pos=%ld", settings->filename,previous_frame->sequence_number, previous_frame->position); } // cleanups on return: CHP(leave_context(settings, true)); // if this was a remap frame we're done if (current_frame->context_status & REMAP_FRAME) { CHP(remap_finished(-cblock->phase)); } settings->sub_name = 0; if (previous_frame->subName) { settings->sub_name = previous_frame->subName; } else { settings->sub_name = NULL; } } else { // call_level == 0 // a definition if (eblock->o_type == O_endsub) { CHKS((settings->defining_sub != 1), NCE_NOT_IN_SUBROUTINE_DEFN); // no longer skipping or defining if (settings->skipping_o) { logOword("case O_endsub in defn -- no longer skipping to:|%s|", settings->skipping_o); settings->skipping_o = NULL; } settings->defining_sub = 0; settings->sub_name = NULL; } } } return status; } // this is executed for m99 in the main program, signifying an endless // loop; m99 main program endless loop is handled in // interp_convert.cc, but because this is like an O-word function // skipping around the file, it's placed here instead. void Interp::loop_to_beginning(setup_pointer settings) { logOword("loop_to_beginning state=%s file=%s", call_statenames[settings->call_state], settings->filename); // scroll back to beginning of file/first block fseek(settings->file_pointer, 0, SEEK_SET); settings->sequence_number = 0; } // // TESTME!!! MORE THOROUGHLY !!!KL // // In the past, calls had to be to predefined subs // // Now they don't. Do things in the following sequence: // 1 -- if o_word is already defined, just go back to it, else // 2 -- if there is a file with the name of the o_word, // open it and start skipping (as in 3, below) // 3 -- skip to the o_word (will be an error if not found) // int Interp::control_back_to( block_pointer block, // pointer to block setup_pointer settings) // pointer to machine settings { static char name[] = "control_back_to"; char newFileName[PATH_MAX+1]; FILE *newFP; offset_map_iterator it; offset_pointer op; logOword("Entered:%s %s", name,basename(block->o_name)); it = settings->offset_map.find(basename(block->o_name)); // #1 already defined if (it != settings->offset_map.end()) { op = &it->second; if ((settings->filename[0] != 0) & (settings->file_pointer == NULL)) { ERS(NCE_FILE_NOT_OPEN); } if (0 != strcmp(settings->filename, op->filename)) { // open the new file... newFP = fopen(op->filename, "r"); // set the line number settings->sequence_number = 0; strncpy(settings->filename, op->filename, sizeof(settings->filename)); if (settings->filename[sizeof(settings->filename)-1] != '\0') { fclose(settings->file_pointer); logOword("filename too long: %s", op->filename); ERS(NCE_UNABLE_TO_OPEN_FILE, op->filename); } if (newFP) { // close the old file... if (settings->file_pointer) // only close if it was open fclose(settings->file_pointer); settings->file_pointer = newFP; } else { logOword("Unable to open file: %s", settings->filename); ERS(NCE_UNABLE_TO_OPEN_FILE,settings->filename); } } if (settings->file_pointer) { // only seek if it was open fseek(settings->file_pointer, op->offset, SEEK_SET); } settings->sequence_number = op->sequence_number; return INTERP_OK; } // #2 open the File newFP = find_ngc_file(settings, block->o_name, newFileName); if (newFP) { logOword("fopen: |%s| OK", newFileName); settings->sequence_number = 0; // close the old file... if (settings->file_pointer) fclose(settings->file_pointer); settings->file_pointer = newFP; strncpy(settings->filename, newFileName, sizeof(settings->filename)); if (settings->filename[sizeof(settings->filename)-1] != '\0') { logOword("new filename '%s' is too long (max len %zu)\n", newFileName, sizeof(settings->filename)-1); settings->filename[sizeof(settings->filename)-1] = '\0'; // oh well, truncate the filename } } else { char *dirname = getcwd(NULL, 0); logOword("fopen: |%s| failed CWD:|%s|", newFileName, dirname); free(dirname); } settings->skipping_o = basename(block->o_name); // start skipping settings->skipping_to_sub = basename(block->o_name); // start skipping settings->skipping_start = settings->sequence_number; return INTERP_OK; } int Interp::handler_returned( setup_pointer settings, context_pointer active_frame, const char *name, bool osub) { int status = INTERP_OK; switch (active_frame->pystuff.impl->py_return_type) { case RET_YIELD: // yield was executed CHP(active_frame->pystuff.impl->py_returned_int); break; case RET_STOPITERATION: // a bare 'return' in a generator - treat as INTERP_OK case RET_NONE: break; case RET_DOUBLE: if (osub) { // float values are ok for osubs settings->return_value = active_frame->pystuff.impl->py_returned_double; settings->value_returned = 1; } else { ERS("handler_returned: %s returned double: %f - invalid", name, active_frame->pystuff.impl->py_returned_double); } break; case RET_INT: if (osub) { // let's be liberal with types - widen to double return value settings->return_value = (double) active_frame->pystuff.impl->py_returned_int; settings->value_returned = 1; } else return active_frame->pystuff.impl->py_returned_int; case RET_ERRORMSG: status = INTERP_ERROR; break; } return status; } // prepare a new call frame. int Interp::enter_context(setup_pointer settings, block_pointer block) { logOword("enter_context cl=%d->%d type=%s", settings->call_level, settings->call_level+1, call_typenames[block->call_type]); settings->call_level++; if (settings->call_level >= INTERP_SUB_ROUTINE_LEVELS) { ERS(NCE_TOO_MANY_SUBROUTINE_LEVELS); } context_pointer frame = &settings->sub_context[settings->call_level]; frame->clear(); // mark frame for finishing remap frame->context_status = (block->call_type == CT_REMAP) ? REMAP_FRAME : 0; frame->subName = block->o_name; frame->pystuff.impl->py_returned_int = 0; frame->pystuff.impl->py_returned_double = 0.0; frame->pystuff.impl->py_return_type = -1; // distinguish call frames: oword,m99,python,remap frame->call_type = block->call_type; return INTERP_OK; } int Interp::leave_context(setup_pointer settings, bool restore) { context_pointer leaving_frame = &settings->sub_context[settings->call_level]; if (settings->call_level < 1) { ERS(NCE_CALL_STACK_UNDERRUN); } logOword("leave_context cl=%d->%d type=%s state=%s" , settings->call_level, settings->call_level-1, call_typenames[leaving_frame->call_type], call_statenames[settings->call_state]); free_named_parameters(leaving_frame); leaving_frame->subName = NULL; settings->call_level--; // drop back if (restore && ((leaving_frame->context_status & (CONTEXT_RESTORE_ON_RETURN|CONTEXT_VALID)) == (CONTEXT_RESTORE_ON_RETURN|CONTEXT_VALID))) { // a valid previous context was marked by an M73 as auto-restore // NB: this means an M71 invalidate context will prevent an // auto-restore on return/endsub CHP(restore_settings(settings, settings->call_level + 1)); } return INTERP_OK; } /************************************************************************/ /* convert_control_functions Returned Value: int (INTERP_OK) Side effects: Changes the flow of control. Called by: execute Calls: control_skip_to control_back_to control_save_offset */ int Interp::convert_control_functions(block_pointer block, // pointer to a block of RS274/NGC instructions setup_pointer settings) // pointer to machine settings { int status = INTERP_OK; context_pointer current_frame; offset_pointer op = NULL; logOword("convert_control_functions %s", o_ops[block->o_type]); // must skip if skipping if (settings->skipping_o && (0 != strcmp(settings->skipping_o, block->o_name))) { logOword("skipping to line: |%s|", settings->skipping_o); return INTERP_OK; } if (settings->skipping_to_sub && (block->o_type != O_sub) && (block->o_type != O_)) { logOword("skipping to sub: |%s|", settings->skipping_to_sub); return INTERP_OK; } // if skipping_o was set, we are now on a line which contains that O-word. // if skipping_to_sub was set, we are now on the 'O-name sub' definition line. switch (block->o_type) { case O_none: // not an error because we use this to signal that we // are not evaluating functions break; case O_sub: case O_: current_frame = &settings->sub_context[settings->call_level]; // Mixing Fanuc- and rs274ngc-style sub calls & defs is not allowed: // - Fanuc: 'O....' subprogram must be called with 'M98' CHKS((block->o_type == O_ && current_frame->call_type != CT_NGC_M98_SUB && current_frame->call_type != CT_NONE), "Fanuc 'O....' subroutine must be called with 'M98'"); // - rs274ngc: 'O.... sub' subprogram must be called with 'O.... call' CHKS((block->o_type == O_sub && current_frame->call_type != CT_NGC_OWORD_SUB && current_frame->call_type != CT_PYTHON_OWORD_SUB && current_frame->call_type != CT_REMAP && current_frame->call_type != CT_NONE), "'O.... sub' subroutine must be called with 'O.... call'"); // if we were skipping, no longer if (settings->skipping_o) { logOword("sub(o_|%s|) was skipping to here", settings->skipping_o); // skipping to a sub means that we must define this now CHP(control_save_offset( block, settings)); logOword("no longer skipping to:|%s|", settings->skipping_o); settings->skipping_o = NULL; // this IS our block number } settings->skipping_to_sub = NULL; // this IS our block number if (block->o_type == O_) { // Done: continue executing into Fanuc-style programs w/o skipping logOword("Entering Fanuc-style program number %s", block->o_name); break; } // if the level is not zero, this is a call // not the definition if (settings->call_level != 0) { logOword("call:%f:%f:%f", settings->parameters[1], settings->parameters[2], settings->parameters[3]); } else { // a definition. We're on the O sub line. logOword("started a rs274-style sub-program defn: %s", block->o_name); CHKS((settings->defining_sub == 1), NCE_NESTED_SUBROUTINE_DEFN); CHP(control_save_offset( block, settings)); // start skipping to the corresponding ensub. settings->skipping_o = block->o_name; settings->skipping_start = settings->sequence_number; settings->defining_sub = 1; settings->sub_name = block->o_name; logOword("will now skip to: |%s|", settings->sub_name); } break; case O_endsub: case O_return: case M_99: // For M99, this only handles return from a subprogram CHKS((block->o_type == M_99 && settings->call_level == 0), "Bug: Reached convert_control_functions() " "from M99 in main program"); if ((settings->call_level == 0) && (settings->sub_name == NULL)) { // detect a standalone 'o