/******************************************************************** * 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 * ********************************************************************/ #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" 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) { // 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", "O_continue_call", "O_pyreturn", }; const char *call_statenames[] = { "CS_NORMAL", "CS_REEXEC_PROLOG", "CS_REEXEC_PYBODY", "CS_REEXEC_EPILOG", "CS_REEXEC_PYOSUB", }; const char *call_typenames[] = { "CT_NGC_OWORD_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_NGC_OWORD_SUB: // copy parameters from context // save old values of parameters // save current file position in context // if we were skipping, no longer if (settings->skipping_o) { logOword("case O_call -- no longer skipping to:|%s|", settings->skipping_o); settings->skipping_o = NULL; } 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]; } // if the previous file was NULL, mark positon as -1 so as not to // reopen it on return. if (settings->file_pointer == NULL) { previous_frame->position = -1; } else { previous_frame->position = ftell(settings->file_pointer); } // save return location 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)); } // transfer control 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]); // positonal args current_frame->tupleargs = bp::tuple(plist); current_frame->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->tupleargs = bp::tuple(plist); current_frame->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->sub_context[settings->call_level - 1]; // 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; break; 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: if (settings->call_level != 0) { // restore subroutine parameters. 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) { settings->file_pointer = NULL; strcpy(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, settings->sub_context[settings->call_level].filename, strerror(errno)); } strcpy(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; } // // 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]; char tmpFileName[PATH_MAX+1]; FILE *newFP; offset_map_iterator it; offset_pointer op; logOword("Entered:%s %s", name,block->o_name); it = settings->offset_map.find(block->o_name); 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; } 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 = get_current_dir_name(); logOword("fopen: |%s| failed CWD:|%s|", newFileName, dirname); free(dirname); ERS(NCE_UNABLE_TO_OPEN_FILE,tmpFileName); } settings->skipping_o = block->o_name; // start skipping settings->skipping_to_sub = 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->py_return_type) { case RET_YIELD: // yield was executed CHP(active_frame->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->py_returned_double; settings->value_returned = 1; } else { ERS("handler_returned: %s returned double: %f - invalid", name, active_frame->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->py_returned_int; settings->value_returned = 1; } else return active_frame->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]; // mark frame for finishing remap frame->context_status = (block->call_type == CT_REMAP) ? REMAP_FRAME : 0; frame->subName = block->o_name; frame->py_returned_int = 0; frame->py_returned_double = 0.0; frame->py_return_type = -1; frame->call_type = block->call_type; // distinguish call frames: oword,python,remap 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)) { 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: // if the level is not zero, this is a call // not the definition // 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 (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 subroutine 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: if ((settings->call_level == 0) && (settings->sub_name == NULL)) { // detect a standalone 'o