GCC Code Coverage Report
Directory: emc/rs274ngc/ Exec Total Coverage
File: emc/rs274ngc/interp_remap.cc Lines: 9 217 4.1 %
Date: 2016-10-27 Branches: 20 842 2.4 %

Line Exec Source
1
/********************************************************************
2
 * Description: interp_remap.cc
3
 *
4
 *  Remapping support
5
 *
6
 * Author: Michael Haberler
7
 * License: GPL Version 2
8
 * System: Linux
9
 *
10
 * Copyright (c) 2011 All rights reserved.
11
 *
12
 ********************************************************************/
13
#ifndef _GNU_SOURCE
14
#define _GNU_SOURCE
15
#endif
16
#include "python_plugin.hh"
17
#include "interp_python.hh"
18
#include <boost/python/list.hpp>
19
namespace bp = boost::python;
20
21
#include <unistd.h>
22
#include <stdio.h>
23
#include <stdlib.h>
24
#include <string.h>
25
#include <ctype.h>
26
#include <sys/types.h>
27
#include <sys/stat.h>
28
#include "rs274ngc.hh"
29
#include "rs274ngc_return.hh"
30
#include "rs274ngc_interp.hh"
31
#include "interp_internal.hh"
32
33
34
35
6553
bool Interp::has_user_mcode(setup_pointer settings,block_pointer block)
36
{
37
    unsigned i;
38
78636
    for(i = 0; i < sizeof(block->m_modes)/sizeof(int); i++) {
39
72083
	if (block->m_modes[i] == -1)
40
	    continue;
41
78
	if (M_REMAPPABLE(block->m_modes[i]) &&
42
11
	    settings->m_remapped[block->m_modes[i]])
43
	    return true;
44
    }
45
    return false;
46
}
47
48
bool Interp::remap_in_progress(const char *code)
49
{
50
    remap_pointer rp = remapping(code);
51
    if (rp == NULL)
52
	return false;
53
    for (int i = _setup.remap_level; i > 0; i--) {
54
	if (_setup.blocks[i].executing_remap == rp) {
55
	    // printf("---------------- remap_in_progress(%s): TRUE level=%d\n",code,i);
56
	    return true;
57
	}
58
    }
59
    // printf("---------------- remap_in_progress(%s): FALSE\n",code);
60
    return false;
61
}
62
63
64
int Interp::convert_remapped_code(block_pointer block,
65
				  setup_pointer settings,
66
				  int phase,
67
				  char letter,
68
				  int number)
69
{
70
    remap_pointer remap;
71
    char key[2];
72
    int status;
73
    block_pointer cblock;
74
    bp::list plist;
75
    char cmd[LINELEN];
76
77
    if (number == -1)
78
	logRemap("convert_remapped_code '%c'", letter);
79
    else
80
	logRemap("convert_remapped_code '%c%d'", letter, number);
81
82
    switch (toupper(letter)) {
83
    case 'M':
84
	remap = settings->m_remapped[number];
85
	break;
86
    case 'G':
87
	remap = settings->g_remapped[number];
88
	break;
89
    default:
90
	key[0] = letter;
91
	key[1] = '\0';
92
	remap = remapping((const char *)key);
93
    }
94
    CHKS((remap == NULL), "BUG: convert_remapped_code: no remapping");
95
96
    // remapped handlers may use Python code to
97
    // setup environment before, and finish work after doing theirs.
98
    // That's what prolog and epilog functions are for.
99
    // These are described in the remap descriptor as read from ini.
100
101
    // Since a remap is always executed in the context of a controlling block,
102
    // this block now contains fields which hold dynamic remap information, like
103
    // the breadcrumbs execution trail.
104
    // Some of these fields are initialized here -
105
    // conceptually the block stack is also a 'remap frame stack'.
106
107
    // the O_call code will pick up the static descriptor and
108
    // dynamic information through the block and call any prolog
109
    // function before passing control to the actual handler procedure.
110
111
    // On the corresponding O_endsub/O_return, any epilog function
112
    // will be executed, doing any work not doable in an NGC file.
113
114
    // Note that even Python-remapped execution is pulled through the
115
    // oword mechanism - so no duplication of handler calling code
116
    // is needed.
117
118
    snprintf(cmd, sizeof(cmd),"O <%s> call ", REMAP_FUNC(remap));
119
120
    // the controlling block holds all dynamic remap information.
121
    cblock = &CONTROLLING_BLOCK(*settings);
122
    cblock->executing_remap = remap; // the current descriptor
123
    cblock->param_cnt = 0;
124
125
    if (remap->argspec && (strchr(remap->argspec, '@') != NULL)) {
126
    	// append a positional argument list instead of local variables
127
    	// if user specified '@'
128
	// named local params are dealt with in execute_call() when
129
	// the new call frame is fully established
130
    	CHP(add_parameters(settings, cblock, &cmd[strlen(cmd)]));
131
    }
132
133
    if ((_setup.debugmask & EMC_DEBUG_REMAP) &&
134
	(_setup.loggingLevel > 2)) {
135
	logRemap("convert_remapped_code(%s)", cmd);
136
    }
137
138
    // good to go, pass to o-word call handling mechanism
139
    status = read(cmd);
140
    block_pointer eblock = &EXECUTING_BLOCK(*settings);
141
    eblock->call_type = CT_REMAP;
142
    CHKS(status != INTERP_OK,
143
	 "convert_remapped_code: inital read returned %s",
144
	 interp_status(status));
145
    return(- phase);
146
}
147
148
149
// add_parameters - a built-in prolog function
150
//
151
// handles argspec and extracts required and optional items from the
152
// controlling block.
153
//
154
// if preparing for an NGC file, add local variables to
155
// the current oword subroutine call frame
156
//
157
// if posargs == NULL:
158
//      add the named parameters as local variables to  the current call frame
159
// if posargs != NULL:
160
//      create a positional argument list as per argspec order
161
//      instead of adding local variables
162
//
163
// also, generate a kwargs style dictionary of required and optional items
164
// in case a Python prolog is called
165
//
166
// 1. add all requried and  present optional words.
167
// 2. error on missing but required words.
168
// 4. handle '>' as to require a positive feed.
169
// 5. handle '^' as to require a positive speed.
170
// 6. handle 'N' as to add the line number.
171
//
172
// return INTERP_ERROR and propagate appropriate message if any errors so far
173
// else return INTERP_OK
174
//
175
// handling '@' (positional params) is dealt with in the calling procedure
176
177
int Interp::add_parameters(setup_pointer settings,
178
			   block_pointer cblock,
179
			   char *posarglist)
180
{
181
    const char *s,*argspec, *code;
182
    block_pointer block;
183
    char missing[30],optional[30],required[30];
184
    char *m = missing;
185
    char *o = optional;
186
    char *r = required;
187
    char msg[LINELEN], tail[LINELEN];
188
    bool errored = false;
189
    remap_pointer rptr = cblock->executing_remap;
190
    context_pointer active_frame = &settings->sub_context[settings->call_level];
191
192
    if (!rptr) {
193
	ERS("BUG: add_parameters: remap_frame: executing_remap == NULL ");
194
    }
195
    code = rptr->name;
196
197
    // if any Python handlers are present, create a kwargs dict
198
    bool pydict = rptr->remap_py || rptr->prolog_func || rptr->epilog_func;
199
200
    std::fill(missing, std::end(missing), 0);
201
    std::fill(optional, std::end(optional), 0);
202
    std::fill(required, std::end(required), 0);
203
    std::fill(msg, std::end(msg), 0);
204
    std::fill(tail, std::end(tail), 0);
205
206
    s = argspec = rptr->argspec;
207
    CHKS((argspec == NULL),"BUG: add_parameters: argspec = NULL");
208
209
    while (*s) {
210
	if (isupper(*s) && !strchr(required,*s)) *r++ = tolower(*s);
211
	if (islower(*s) && !strchr(optional,*s)) *o++ = *s;
212
	if (strchr(">^Nn",*s) && !strchr(required,*s)) *r++ = *s;
213
	s++;
214
    }
215
    block = &CONTROLLING_BLOCK((*settings));
216
217
    logNP("add_parameters code=%s argspec=%s call_level=%d r=%s o=%s pydict=%d\n",
218
	    code,argspec,settings->call_level,required,optional,pydict);
219
220
#define STORE(name,value)						\
221
    if (pydict) {							\
222
	try {								\
223
	    active_frame->pystuff.impl->kwargs[name] = value;		\
224
        }								\
225
        catch (bp::error_already_set) {					\
226
	    PyErr_Print();						\
227
	    PyErr_Clear();						\
228
	    ERS("add_parameters: cant add '%s' to args",name);		\
229
	}								\
230
    }									\
231
    if (posarglist) {							\
232
	char actual[LINELEN];						\
233
	snprintf(actual, sizeof(actual),"[%.4lf]", value);		\
234
	strcat(posarglist, actual);					\
235
	cblock->param_cnt++;						\
236
    } else {								\
237
	add_named_param(name,0);					\
238
	store_named_param(settings,name,value,0);			\
239
    }
240
241
242
#define PARAM(spec,name,flag,value) 	                    	\
243
    if ((flag)) { /* present */	                    	        \
244
	/* required or optional */ 	                    	\
245
	if (strchr(required,spec) || strchr(optional,spec)) {	\
246
	    STORE(name,value);					\
247
	}							\
248
    } else {							\
249
	if (strchr(required,spec)) { /* missing */		\
250
	    *m++ = spec;					\
251
	    errored = true;					\
252
	}							\
253
    }
254
255
    s =  rptr->argspec;
256
    // step through argspec in order so positional args are built
257
    // in the correct order
258
    while (*s) {
259
	switch (tolower(*s)) {
260
	case 'a' : PARAM('a',"a",block->a_flag,block->a_number); break;
261
	case 'b' : PARAM('b',"b",block->b_flag,block->b_number); break;
262
	case 'c' : PARAM('c',"c",block->c_flag,block->c_number); break;
263
	case 'd' : PARAM('d',"d",block->d_flag,block->d_number_float); break;
264
	case 'e' : PARAM('e',"e",block->e_flag,block->e_number); break;
265
	case 'f' : PARAM('f',"f",block->f_flag,block->f_number); break;
266
	case 'h' : PARAM('h',"h",block->h_flag,(double) block->h_number); break;
267
	case 'i' : PARAM('i',"i",block->i_flag,block->i_number); break;
268
	case 'j' : PARAM('j',"j",block->j_flag,block->j_number); break;
269
	case 'k' : PARAM('k',"k",block->k_flag,block->k_number); break;
270
	case 'l' : PARAM('l',"l",block->l_flag,(double) block->l_number); break;
271
	case 'p' : PARAM('p',"p",block->p_flag,block->p_number); break;
272
	case 'q' : PARAM('q',"q",block->q_flag,block->q_number); break;
273
	case 'r' : PARAM('r',"r",block->r_flag,block->r_number); break;
274
	case 's' : PARAM('s',"s",block->s_flag,block->s_number); break;
275
	case 't' : PARAM('t',"t",block->t_flag, (double) block->t_number); break;
276
	case 'u' : PARAM('u',"u",block->u_flag,block->u_number); break;
277
	case 'v' : PARAM('v',"v",block->v_flag,block->v_number); break;
278
	case 'w' : PARAM('w',"w",block->w_flag,block->w_number); break;
279
	case 'x' : PARAM('x',"x",block->x_flag,block->x_number); break;
280
	case 'y' : PARAM('y',"y",block->y_flag,block->y_number); break;
281
	case 'z' : PARAM('z',"z",block->z_flag,block->z_number); break;
282
	case '-' : break; // ignore - backwards compatibility
283
	default: ;
284
	}
285
	s++;
286
    }
287
288
    s = missing;
289
    if (*s) {
290
	strcat(tail," missing: ");
291
    }
292
    while (*s) {
293
	errored = true;
294
	char c  = toupper(*s);
295
	strncat(tail,&c,1);
296
	if (*(s+1)) strcat(tail,",");
297
	s++;
298
    }
299
    // special cases:
300
    // N...add line number
301
    if (strchr(required,'n') || strchr(required,'N')) {
302
	STORE("n",(double) cblock->saved_line_number);
303
    }
304
305
    // >...require positive feed
306
    if (strchr(required,'>')) {
307
	if (settings->feed_rate > 0.0) {
308
	    STORE("f",settings->feed_rate);
309
	} else {
310
	    strcat(tail,"F>0,");
311
	    errored = true;
312
	}
313
    }
314
    // ^...require positive speed
315
    if (strchr(required,'^')) {
316
	if (settings->speed > 0.0) {
317
	    STORE("s",settings->speed);
318
	} else {
319
	    strcat(tail,"S>0,");
320
	    errored = true;
321
	}
322
    }
323
324
    if (errored) {
325
	ERS("user-defined %s:%s",
326
	    code, tail);
327
    }
328
    return INTERP_OK;
329
}
330
331
332
// this looks up a remapping by unnormalized code (like G88.1)
333
600
remap_pointer Interp::remapping(const char *code)
334
{
335
1200
    remap_iterator n = 	_setup.remaps.find(code);
336
1200
    if (n !=  _setup.remaps.end())
337
	return &n->second;
338
    else
339
	return NULL;
340
}
341
342
// parse options of the form:
343
// REMAP= M420 modalgroup=6 argspec=pq prolog=setnamedvars ngc=m43.ngc epilog=ignore_retvalue
344
// REMAP= M421 modalgroup=6 argspec=- prolog=setnamedvars python=m43func epilog=ignore_retvalue
345
346
int Interp::parse_remap(const char *inistring, int lineno)
347
{
348
349
    char iniline[LINELEN];
350
    char *argv[MAX_REMAPOPTS];
351
    int   argc = 0;
352
    const char *code;
353
    remap r;
354
    bool errored = false;
355
    int g1 = 0, g2 = 0;
356
    int mcode = -1;
357
    int gcode = -1;
358
    char *s;
359
360
    memset((void *)&r, 0, sizeof(remap));
361
    r.modal_group = -1; // mark as unset, required param for m/g
362
    r.motion_code = INT_MIN;
363
    strcpy(iniline, inistring);
364
    // strip trailing comments
365
    if ((s = strchr(iniline, '#')) != NULL) {
366
	*s = '\0';
367
    }
368
    s = strtok((char *) iniline, " \t");
369
370
    while( s != NULL && argc < MAX_REMAPOPTS - 1) {
371
	argv[argc++] = s;
372
	s = strtok( NULL, " \t" );
373
    }
374
    if (argc == MAX_REMAPOPTS) {
375
	Error("parse_remap: too many arguments (max %d)", MAX_REMAPOPTS);
376
	goto fail;
377
    }
378
    argv[argc] = NULL;
379
    code = strstore(argv[0]);
380
    r.name = code;
381
382
    for (int i = 1; i < argc; i++) {
383
	int kwlen = 0;
384
	char *kw = argv[i];
385
	char *arg = strchr(argv[i],'=');
386
	if (arg != NULL) {
387
	    kwlen = arg - argv[i];
388
	    arg++;
389
	    if (!strlen(arg)) { // 'kw='
390
		Error("option '%s' - zero length value: %d:REMAP = %s",
391
		      kw,lineno,inistring);
392
		errored = true;
393
		continue;
394
	    }
395
	} else { // 'kw'
396
	    Error("option '%s' - missing '=<value>: %d:REMAP = %s",
397
		  kw,lineno,inistring);
398
	    errored = true;
399
	    continue;;
400
	}
401
	if (!strncasecmp(kw,"modalgroup",kwlen)) {
402
	    r.modal_group = atoi(arg);
403
	    continue;
404
	}
405
	if (!strncasecmp(kw,"argspec",kwlen)) {
406
	    size_t pos = strspn (arg,
407
				 "ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijklmnpqrstuvwxyz>^@");
408
	    if (pos != strlen(arg)) {
409
		Error("argspec: illegal word '%c' - %d:REMAP = %s",
410
		      arg[pos],lineno,inistring);
411
		errored = true;
412
		continue;
413
	    }
414
	    r.argspec = strstore(arg);
415
	    continue;
416
	}
417
	if (!strncasecmp(kw,"prolog",kwlen)) {
418
	    if (PYUSABLE) {
419
		r.prolog_func = strstore(arg);
420
	    } else {
421
		Error("Python plugin required for prolog=, but not available: %d:REMAP = %s",
422
		      lineno,inistring);
423
		errored = true;
424
		continue;
425
	    }
426
	    continue;
427
	}
428
	if (!strncasecmp(kw,"epilog",kwlen)) {
429
	    if (PYUSABLE) {
430
		r.epilog_func = strstore(arg);
431
	    } else {
432
		Error("Python plugin required for epilog=, but not available: %d:REMAP = %s",
433
		      lineno,inistring);
434
		errored = true;
435
		continue;
436
	    }
437
	    continue;
438
	}
439
	if (!strncasecmp(kw,"ngc",kwlen)) {
440
	    if (r.remap_py) {
441
		Error("cant remap to an ngc file and a Python function: -  %d:REMAP = %s",
442
		      lineno,inistring);
443
		errored = true;
444
		continue;
445
	    }
446
	    FILE *fp = find_ngc_file(&_setup,arg);
447
	    if (fp) {
448
		r.remap_ngc = strstore(arg);
449
		fclose(fp);
450
	    } else {
451
		Error("NGC file not found: ngc=%s - %d:REMAP = %s",
452
		      arg, lineno,inistring);
453
		errored = true;
454
	    }
455
	    continue;
456
	}
457
	if (!strncasecmp(kw,"python",kwlen)) {
458
	    if (r.remap_ngc ) {
459
		Error("cant remap to an ngc file and a Python function: -  %d:REMAP = %s",
460
		      lineno,inistring);
461
		errored = true;
462
		continue;
463
	    }
464
	    if (!PYUSABLE) {
465
		Error("Python plugin required for python=, but not available: %d:REMAP = %s",
466
		      lineno,inistring);
467
		errored = true;
468
		continue;
469
	    }
470
	    if (!is_pycallable(&_setup, REMAP_MODULE, arg)) {
471
		Error("'%s' is not a Python callable function - %d:REMAP = %s",
472
		      arg,lineno,inistring);
473
		errored = true;
474
		continue;
475
	    }
476
	    r.remap_py = strstore(arg);
477
	    continue;
478
	}
479
	Error("unrecognized option '%*s' in  %d:REMAP = %s",
480
	      kwlen,kw,lineno,inistring);
481
    }
482
    if (errored) {
483
	goto fail;
484
    }
485
486
    if (remapping(code)) {
487
	Error("code '%s' already remapped : %d:REMAP = %s",
488
	      code,lineno,inistring);
489
	goto fail;
490
    }
491
492
    // it is an error not to define a remap function to call.
493
    if ((r.remap_ngc == NULL) && (r.remap_py == NULL)) {
494
	Error("code '%s' - no remap function given, use either 'python=<function>' or 'ngc=<basename>' : %d:REMAP = %s",
495
	      code,lineno,inistring);
496
	goto fail;
497
    }
498
499
#define CHECK(bad, fmt, ...)			\
500
    do {					\
501
	if (bad) {				\
502
	    Log(fmt, ## __VA_ARGS__);		\
503
	    goto fail;				\
504
	}					\
505
    } while(0)
506
507
    switch (towlower(*code)) {
508
509
    case 't':
510
    case 's':
511
    case 'f':
512
	CHECK((strlen(code) > 1),"%d: %c remap - only single letter code allowed", lineno, *code);
513
	CHECK((r.modal_group != -1), "%d: %c remap - modal group setting ignored - fixed sequencing", lineno, *code);
514
	_setup.remaps[code] = r;
515
	break;
516
517
    case 'm':
518
	if (sscanf(code + 1, "%d", &mcode) == 1) {
519
	    _setup.remaps[code] = r;
520
	    _setup.m_remapped[mcode] = &_setup.remaps[code];
521
	} else {
522
	    Error("parsing M-code: expecting integer like 'M420', got '%s' : %d:REMAP = %s",
523
		  code,lineno,inistring);
524
	    goto fail;
525
	}
526
	if (r.modal_group == -1) {
527
	    Error("warning: code '%s' : no modalgroup=<int> given, using default group %d : %d:REMAP = %s",
528
		  code, MCODE_DEFAULT_MODAL_GROUP,lineno,inistring);
529
	    r.modal_group = MCODE_DEFAULT_MODAL_GROUP;
530
	}
531
	if (!M_MODE_OK(r.modal_group)) {
532
	    Error("error: code '%s' : invalid modalgroup=<int> given (currently valid: 4..10) : %d:REMAP = %s",
533
		  code,lineno,inistring);
534
	    goto fail;
535
	}
536
	break;
537
    case 'g':
538
539
	// code may be G88.1 or so - normalize to use 'G881' instead
540
	// (multiply by 10, no dots)
541
	if (sscanf(code + 1, "%d.%d", &g1, &g2) == 2) {
542
	    gcode = g1 * 10 + g2;
543
	}
544
	if ( gcode == -1) {
545
	    if (sscanf(code + 1, "%d", &gcode) != 1) {
546
		Error("code '%s' : cant parse G-code : %d:REMAP = %s",
547
		      code, lineno, inistring);
548
		goto fail;
549
	    }
550
	    gcode *= 10;
551
	}
552
	r.motion_code = gcode;
553
	if (r.modal_group == -1) {
554
	    Error("warning: code '%s' : no modalgroup=<int> given, using default group %d : %d:REMAP = %s",
555
		  code, GCODE_DEFAULT_MODAL_GROUP, lineno, inistring);
556
	    r.modal_group = GCODE_DEFAULT_MODAL_GROUP;
557
	    break;
558
	}
559
	if (!G_MODE_OK(r.modal_group)) {
560
	    Error("error: code '%s' : %s modalgroup=<int> given  : %d:REMAP = %s",
561
		  argv[0],
562
		  r.modal_group == -1 ? "no" : "invalid",
563
		  lineno,
564
		  inistring);
565
	    goto fail;
566
	}
567
	_setup.remaps[code] = r;
568
	_setup.g_remapped[gcode] = &_setup.remaps[code];
569
	break;
570
571
    default:
572
	// make sure the python plugin is in a usable state if needed
573
	if ((r.prolog_func || r.remap_py || r.epilog_func) &&
574
	    (!PYUSABLE))  {
575
	    fprintf(stderr, "fatal: REMAP requires the Python plugin, which did not initialize\n");
576
	    break;
577
	}
578
	Log("REMAP BUG=%s %d:REMAP = %s",
579
	    code,lineno,inistring);
580
    }
581
    return INTERP_OK;
582
583
 fail:
584
    return INTERP_ERROR;
585
207
}