1 /* praat_script.cpp
2  *
3  * Copyright (C) 1993-2021 Paul Boersma
4  *
5  * This code is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or (at
8  * your option) any later version.
9  *
10  * This code is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13  * See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this work. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "praatP.h"
20 #include "praat_script.h"
21 #include "sendpraat.h"
22 #include "sendsocket.h"
23 #include "UiPause.h"
24 #include "DemoEditor.h"
25 
praat_findObjectFromString(Interpreter interpreter,conststring32 string)26 static integer praat_findObjectFromString (Interpreter interpreter, conststring32 string) {
27 	try {
28 		integer IOBJECT;
29 		while (*string == U' ') string ++;
30 		if (*string >= U'A' && *string <= U'Z') {
31 			/*
32 				Find the object by its name.
33 			*/
34 			static MelderString buffer;
35 			MelderString_copy (& buffer, string);
36 			char32 *space = str32chr (buffer.string, U' ');
37 			if (! space)
38 				Melder_throw (U"Missing space in name.");
39 			*space = U'\0';
40 			char32 *className = & buffer.string [0], *givenName = space + 1;
41 			WHERE_DOWN (1) {
42 				Daata object = (Daata) OBJECT;
43 				if (str32equ (className, Thing_className (OBJECT)) && str32equ (givenName, object -> name.get()))
44 					return IOBJECT;
45 			}
46 			/*
47 				No object with that name. Perhaps the class name was wrong?
48 			*/
49 			ClassInfo klas = Thing_classFromClassName (className, nullptr);
50 			WHERE_DOWN (1) {
51 				Daata object = (Daata) OBJECT;
52 				if (str32equ (klas -> className, Thing_className (OBJECT)) && str32equ (givenName, object -> name.get()))
53 					return IOBJECT;
54 			}
55 			Melder_throw (U"No object with that name.");
56 		} else {
57 			/*
58 				Find the object by its ID.
59 			*/
60 			double value;
61 			Interpreter_numericExpression (interpreter, string, & value);
62 			integer id = (integer) value;
63 			WHERE (ID == id)
64 				return IOBJECT;
65 			Melder_throw (U"No object with number ", id, U".");
66 		}
67 	} catch (MelderError) {
68 		Melder_throw (U"Object \"", string, U"\" does not exist.");
69 	}
70 }
71 
praat_findEditorFromString(conststring32 string)72 Editor praat_findEditorFromString (conststring32 string) {
73 	integer IOBJECT;
74 	while (*string == U' ')
75 		string ++;
76 	if (*string >= U'A' && *string <= U'Z') {
77 		WHERE_DOWN (1) {
78 			for (int ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++) {
79 				Editor editor = theCurrentPraatObjects -> list [IOBJECT]. editors [ieditor];
80 				if (editor) {
81 					Melder_assert (editor -> name);
82 					const char32 *space = str32chr (editor -> name.get(), U' ');   // editors tend to be called like "3. Sound kanweg"
83 					if (space) {   // but not all
84 						conststring32 name = space + 1;
85 						if (str32equ (name, string))
86 							return editor;
87 					}
88 				}
89 			}
90 		}
91 	} else {
92 		WHERE_DOWN (1) {
93 			for (int ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++) {
94 				Editor editor = theCurrentPraatObjects -> list [IOBJECT]. editors [ieditor];
95 				if (editor && str32equ (editor -> name.get(), string))
96 					return editor;
97 			}
98 		}
99 	}
100 	Melder_throw (U"Editor \"", string, U"\" does not exist.");
101 }
102 
praat_findEditorById(integer id)103 Editor praat_findEditorById (integer id) {
104 	int IOBJECT;
105 	WHERE (1) {
106 		if (ID == id) {
107 			for (int ieditor = 0; ieditor < praat_MAXNUM_EDITORS; ieditor ++) {
108 				Editor editor = theCurrentPraatObjects -> list [IOBJECT]. editors [ieditor];
109 				if (editor)
110 					return editor;
111 			}
112 		}
113 	}
114 	Melder_throw (U"Editor ", id, U" does not exist.");
115 }
116 
parseCommaSeparatedArguments(Interpreter interpreter,char32 * arguments,structStackel * args)117 static int parseCommaSeparatedArguments (Interpreter interpreter, char32 *arguments, structStackel *args) {
118 	int narg = 0, depth = 0;
119 	for (char32 *p = arguments; ; p ++) {
120 		bool endOfArguments = *p == U'\0';
121 		if (endOfArguments || (*p == U',' && depth == 0)) {
122 			if (narg == MAXIMUM_NUMBER_OF_FIELDS)
123 				Melder_throw (U"Cannot have more than ", MAXIMUM_NUMBER_OF_FIELDS, U" arguments");
124 			*p = U'\0';
125 			Formula_Result result;
126 			Interpreter_anyExpression (interpreter, arguments, & result);
127 			narg ++;
128 			/*
129 				First remove the old contents.
130 			*/
131 			args [narg]. reset();
132 			#if STACKEL_VARIANTS_ARE_PACKED_IN_A_UNION
133 				memset (& args [narg], 0, sizeof (structStackel));
134 			#endif
135 			/*
136 				Then copy in the new contents.
137 			*/
138 			switch (result. expressionType) {
139 				case kFormula_EXPRESSION_TYPE_NUMERIC: {
140 					args [narg]. which = Stackel_NUMBER;
141 					args [narg]. number = result. numericResult;
142 				} break; case kFormula_EXPRESSION_TYPE_STRING: {
143 					args [narg]. setString (result. stringResult.move());
144 				} break; case kFormula_EXPRESSION_TYPE_NUMERIC_VECTOR: {
145 					args [narg]. which = Stackel_NUMERIC_VECTOR;
146 					args [narg]. numericVector = result. numericVectorResult;
147 					args [narg]. owned = result. owned;
148 					result. owned = false;
149 				} break; case kFormula_EXPRESSION_TYPE_NUMERIC_MATRIX: {
150 					args [narg]. which = Stackel_NUMERIC_MATRIX;
151 					args [narg]. numericMatrix = result. numericMatrixResult;
152 					args [narg]. owned = result. owned;
153 					result. owned = false;
154 				} break; case kFormula_EXPRESSION_TYPE_STRING_ARRAY: {
155 					args [narg]. which = Stackel_STRING_ARRAY;
156 					args [narg]. stringArray = result. stringArrayResult;
157 					args [narg]. owned = result. owned;
158 					result. owned = false;
159 				} break;
160 			}
161 			arguments = p + 1;
162 		} else if (*p == U'(' || *p == U'[' || *p == U'{') {
163 			depth ++;
164 		} else if (*p == U')' || *p == U']' || *p == U'}') {
165 			depth --;
166 		} else if (*p == U'\"') {
167 			for (;;) {
168 				p ++;
169 				if (*p == U'\"') {
170 					if (p [1] == U'\"')
171 						p ++;
172 					else
173 						break;
174 				}
175 			}
176 		}
177 		if (endOfArguments)
178 			break;
179 	}
180 	return narg;
181 }
182 
praat_executeCommand(Interpreter interpreter,char32 * command)183 bool praat_executeCommand (Interpreter interpreter, char32 *command) {
184 	if (interpreter)
185 		interpreter -> returnType = kInterpreter_ReturnType::VOID_;   // clear return type to its default
186 
187 	static struct structStackel args [1 + MAXIMUM_NUMBER_OF_FIELDS];
188 	//trace (U"praat_executeCommand: ", Melder_pointer (interpreter), U": ", command);
189 	if (command [0] == U'\0' || command [0] == U'#' || command [0] == U'!' || command [0] == U';')
190 		/* Skip empty lines and comments. */;
191 	else if ((command [0] == U'.' || command [0] == U'+' || command [0] == U'-') && Melder_isAsciiUpperCaseLetter (command [1])) {   // selection?
192 		integer IOBJECT = praat_findObjectFromString (interpreter, command + 1);
193 		if (command [0] == '.')
194 			praat_deselectAll ();
195 		if (command [0] == '-')
196 			praat_deselect (IOBJECT);
197 		else
198 			praat_select (IOBJECT);
199 		praat_show ();
200 	} else if (Melder_isLetter (command [0]) && ! Melder_isUpperCaseLetter (command [0])) {   // all directives start with an ASCII lower-case letter
201 		if (str32nequ (command, U"select ", 7)) {
202 			if (str32nequ (command + 7, U"all", 3) && (command [10] == U'\0' || command [10] == U' ' || command [10] == U'\t')) {
203 				praat_selectAll ();
204 				praat_show ();
205 			} else {
206 				integer IOBJECT = praat_findObjectFromString (interpreter, command + 7);
207 				praat_deselectAll ();
208 				praat_select (IOBJECT);
209 				praat_show ();
210 			}
211 		} else if (str32nequ (command, U"plus ", 5)) {
212 			integer IOBJECT = praat_findObjectFromString (interpreter, command + 5);
213 			praat_select (IOBJECT);
214 			praat_show ();
215 		} else if (str32nequ (command, U"minus ", 6)) {
216 			integer IOBJECT = praat_findObjectFromString (interpreter, command + 6);
217 			praat_deselect (IOBJECT);
218 			praat_show ();
219 		} else if (str32nequ (command, U"echo ", 5)) {
220 			MelderInfo_open ();
221 			MelderInfo_write (command + 5);
222 			MelderInfo_close ();
223 		} else if (str32nequ (command, U"clearinfo", 9)) {
224 			Melder_clearInfo ();
225 		} else if (str32nequ (command, U"print ", 6)) {
226 			MelderInfo_write (command + 6);
227 			MelderInfo_drain ();
228 		} else if (str32nequ (command, U"printtab", 8)) {
229 			MelderInfo_write (U"\t");
230 			MelderInfo_drain ();
231 		} else if (str32nequ (command, U"printline", 9)) {
232 			if (command [9] == ' ') MelderInfo_write (command + 10);
233 			MelderInfo_write (U"\n");
234 			MelderInfo_drain ();
235 		} else if (str32nequ (command, U"fappendinfo ", 12)) {
236 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
237 				Melder_throw (U"The script command \"fappendinfo\" is not available inside pictures.");
238 			structMelderFile file { };
239 			Melder_relativePathToFile (command + 12, & file);
240 			MelderFile_appendText (& file, Melder_getInfo ());
241 		} else if (str32nequ (command, U"unix ", 5)) {
242 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
243 				Melder_throw (U"The script command \"unix\" is not available inside manuals.");
244 			try {
245 				Melder_system (command + 5);
246 			} catch (MelderError) {
247 				Melder_throw (U"Unix command \"", command + 5, U"\" returned error status;\n"
248 					U"if you want to ignore this, use `unix_nocheck' instead of `unix'.");
249 			}
250 		} else if (str32nequ (command, U"unix_nocheck ", 13)) {
251 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
252 				Melder_throw (U"The script command \"unix_nocheck\" is not available inside manuals.");
253 			try {
254 				Melder_system (command + 13);
255 			} catch (MelderError) {
256 				Melder_clearError ();
257 			}
258 		} else if (str32nequ (command, U"system ", 7)) {
259 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
260 				Melder_throw (U"The script command \"system\" is not available inside manuals.");
261 			try {
262 				Melder_system (command + 7);
263 			} catch (MelderError) {
264 				Melder_throw (U"System command \"", command + 7, U"\" returned error status;\n"
265 					U"if you want to ignore this, use `system_nocheck' instead of `system'.");
266 			}
267 		} else if (str32nequ (command, U"system_nocheck ", 15)) {
268 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
269 				Melder_throw (U"The script command \"system_nocheck\" is not available inside manuals.");
270 			try {
271 				Melder_system (command + 15);
272 			} catch (MelderError) {
273 				Melder_clearError ();
274 			}
275 		} else if (str32nequ (command, U"nowarn ", 7)) {
276 			autoMelderWarningOff nowarn;
277 			return praat_executeCommand (interpreter, command + 7);
278 		} else if (str32nequ (command, U"noprogress ", 11)) {
279 			autoMelderProgressOff noprogress;
280 			return praat_executeCommand (interpreter, command + 11);
281 		} else if (str32nequ (command, U"nocheck ", 8)) {
282 			try {
283 				return praat_executeCommand (interpreter, command + 8);
284 			} catch (MelderError) {
285 				Melder_clearError ();
286 				return false;
287 			}
288 		} else if (str32nequ (command, U"demo ", 5)) {
289 			autoDemoOpen demo;
290 			return praat_executeCommand (interpreter, command + 5);
291 		} else if (str32nequ (command, U"asynchronous ", 13)) {
292 			autoMelderAsynchronous asynchronous;
293 			return praat_executeCommand (interpreter, command + 13);
294 		} else if (str32nequ (command, U"pause ", 6) || str32equ (command, U"pause")) {
295 			if (theCurrentPraatApplication -> batch)
296 				return true;   // in batch we ignore pause statements
297 			UiPause_begin (theCurrentPraatApplication -> topShell, U"stop or continue", interpreter);
298 			UiPause_comment (str32equ (command, U"pause") ? U"..." : command + 6);
299 			UiPause_end (1, 1, 0, U"Continue", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, interpreter);
300 		} else if (str32nequ (command, U"execute ", 8)) {
301 			praat_executeScriptFromFileNameWithArguments (command + 8);
302 		} else if (str32nequ (command, U"editor", 6)) {
303 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
304 				Melder_throw (U"The script command \"editor\" is not available inside manuals.");
305 			if (command [6] == U' ' && Melder_isLetter (command [7])) {
306 				praatP. editor = praat_findEditorFromString (command + 7);
307 			} else if (command [6] == U'\0') {
308 				if (interpreter && interpreter -> editorClass) {
309 					praatP. editor = praat_findEditorFromString (interpreter -> environmentName.get());
310 				} else {
311 					Melder_throw (U"The function \"editor\" requires an argument when called from outside an editor.");
312 				}
313 			} else {
314 				Interpreter_voidExpression (interpreter, command);
315 			}
316 		} else if (str32nequ (command, U"endeditor", 9)) {
317 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
318 				Melder_throw (U"The script command \"endeditor\" is not available inside manuals.");
319 			praatP. editor = nullptr;
320 		} else if (str32nequ (command, U"sendpraat ", 10)) {
321 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
322 				Melder_throw (U"The script command \"sendpraat\" is not available inside manuals.");
323 			char32 programName [41], *q = & programName [0];
324 			#ifdef macintosh
325 				#define SENDPRAAT_TIMEOUT  10
326 			#else
327 				#define SENDPRAAT_TIMEOUT  0
328 			#endif
329 			const char32 *p = command + 10;
330 			while (*p == U' ' || *p == U'\t') p ++;
331 			while (*p != U'\0' && *p != U' ' && *p != U'\t' && q < programName + 39)
332 				*q ++ = *p ++;
333 			*q = U'\0';
334 			if (q == & programName [0])
335 				Melder_throw (U"Missing program name after `sendpraat'.");
336 			while (*p == U' ' || *p == U'\t') p ++;
337 			if (*p == U'\0')
338 				Melder_throw (U"Missing command after `sendpraat'.");
339 			#if motif
340 			char *result = sendpraat (XtDisplay (theCurrentPraatApplication -> topShell), Melder_peek32to8 (programName),
341 				SENDPRAAT_TIMEOUT, Melder_peek32to8 (p));
342 			if (result)
343 				Melder_throw (Melder_peek8to32 (result), U"\nMessage to ", programName, U" not completed.");
344 			#endif
345 		} else if (str32nequ (command, U"sendsocket ", 11)) {
346 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
347 				Melder_throw (U"The script command \"sendsocket\" is not available inside manuals.");
348 			char32 hostName [61], *q = & hostName [0];
349 			const char32 *p = command + 11;
350 			while (*p == U' ' || *p == U'\t')
351 				p ++;
352 			while (*p != U'\0' && *p != U' ' && *p != U'\t' && q < hostName + 59)
353 				*q ++ = *p ++;
354 			*q = U'\0';
355 			if (q == hostName)
356 				Melder_throw (U"Missing host name after `sendsocket'.");
357 			while (*p == U' ' || *p == U'\t') p ++;
358 			if (*p == U'\0')
359 				Melder_throw (U"Missing command after `sendsocket'.");
360 			char *result = sendsocket (Melder_peek32to8 (hostName), Melder_peek32to8 (p));
361 			if (result)
362 				Melder_throw (Melder_peek8to32 (result), U"\nMessage to ", hostName, U" not completed.");
363 		} else if (str32nequ (command, U"filedelete ", 11)) {
364 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
365 				Melder_throw (U"The script command \"filedelete\" is not available inside manuals.");
366 			const char32 *p = command + 11;
367 			structMelderFile file { };
368 			while (*p == U' ' || *p == U'\t') p ++;
369 			if (*p == U'\0')
370 				Melder_throw (U"Missing file name after `filedelete'.");
371 			Melder_relativePathToFile (p, & file);
372 			MelderFile_delete (& file);
373 		} else if (str32nequ (command, U"fileappend ", 11)) {
374 			if (theCurrentPraatObjects != & theForegroundPraatObjects)
375 				Melder_throw (U"The script command \"fileappend\" is not available inside manuals.");
376 			const char32 *p = command + 11;
377 			char32 path [kMelder_MAXPATH+1], *q = & path [0];
378 			while (*p == U' ' || *p == U'\t') p ++;
379 			if (*p == U'\0')
380 				Melder_throw (U"Missing file name after `fileappend'.");
381 			if (*p == '\"') {
382 				for (;;) {
383 					char32 kar = * ++ p;
384 					if (kar == U'\"') if (* ++ p == U'\"') *q ++ = U'\"'; else break;
385 					else if (kar == U'\0') break;
386 					else *q ++ = kar;
387 				}
388 			} else {
389 				for (;;) {
390 					char32 kar = * p;
391 					if (kar == U'\0' || kar == U' ' || kar == U'\t') break;
392 					*q ++ = kar;
393 					p ++;
394 				}
395 			}
396 			*q = U'\0';
397 			if (*p == U' ' || *p == U'\t') {
398 				structMelderFile file { };
399 				Melder_relativePathToFile (path, & file);
400 				MelderFile_appendText (& file, p + 1);
401 			}
402 		} else {
403 			/*
404 			 * This must be a formula command:
405 			 *    proc (args)
406 			 */
407 			Interpreter_voidExpression (interpreter, command);
408 		}
409 	} else {   /* Simulate menu choice. */
410 		bool hasDots = false, hasColon = false;
411 
412  		/* Parse command line into command and arguments. */
413 		/* The separation is formed by the three dots or a colon. */
414 
415 		char32 *arguments = & command [0];
416 		for (arguments = & command [0]; *arguments != U'\0'; arguments ++) {
417 			if (*arguments == U':') {
418 				hasColon = true;
419 				if (arguments [1] == U'\0') {
420 					arguments = & arguments [1];   // empty string
421 				} else {
422 					if (arguments [1] != U' ') {
423 						Melder_throw (U"There should be a space after the colon.");
424 					}
425 					arguments [1] = U'\0';   // new end of "command"
426 					arguments += 2;   // the arguments start after the space
427 				}
428 				break;
429 			}
430 			if (*arguments == U'.' && arguments [1] == U'.' && arguments [2] == U'.') {
431 				hasDots = true;
432 				arguments += 3;
433 				if (*arguments == U'\0') {
434 					// empty string
435 				} else {
436 					if (*arguments != U' ') {
437 						Melder_throw (U"There should be a space after the three dots.");
438 					}
439 					*arguments = U'\0';   // new end of "command"
440 					arguments ++;   // the arguments start after the space
441 				}
442 				break;
443 			}
444 		}
445 
446 		/* See if command exists and is available; ignore separators. */
447 		/* First try loose commands, then fixed commands. */
448 
449 		integer narg;
450 		char32 command2 [200];
451 		if (hasColon) {
452 			narg = parseCommaSeparatedArguments (interpreter, arguments, args);
453 			str32cpy (command2, command);
454 			char32 *colon = str32chr (command2, U':');
455 			colon [0] = colon [1] = colon [2] = U'.';
456 			colon [3] = U'\0';
457 		}
458 		if (theCurrentPraatObjects == & theForegroundPraatObjects && praatP. editor) {
459 			if (hasColon) {
460 				Editor_doMenuCommand (praatP. editor, command2, narg, args, nullptr, interpreter);
461 			} else {
462 				Editor_doMenuCommand (praatP. editor, command, 0, nullptr, arguments, interpreter);
463 			}
464 		} else if (theCurrentPraatObjects != & theForegroundPraatObjects &&
465 		    (str32nequ (command, U"Save ", 5) ||
466 			 str32nequ (command, U"Write ", 6) ||
467 			 str32nequ (command, U"Append ", 7) ||
468 			 str32equ (command, U"Quit")))
469 		{
470 			Melder_throw (U"Commands that write files (including Quit) are not available inside manuals.");
471 		} else {
472 			bool theCommandIsAnExistingAction = false;
473 			try {
474 				if (hasColon) {
475 					theCommandIsAnExistingAction = praat_doAction (command2, narg, args, interpreter);
476 				} else {
477 					theCommandIsAnExistingAction = praat_doAction (command, arguments, interpreter);
478 				}
479 			} catch (MelderError) {
480 				/*
481 				 * We only get here if the command *was* an existing action.
482 				 * Anything could have gone wrong in its execution,
483 				 * but one invisible problem can be checked here.
484 				 */
485 				if (hasDots && arguments [0] != U'\0' && arguments [str32len (arguments) - 1] == U' ') {
486 					Melder_throw (U"It may be helpful to remove the trailing spaces in \"", arguments, U"\".");
487 				} else {
488 					throw;
489 				}
490 			}
491 			if (! theCommandIsAnExistingAction) {
492 				bool theCommandIsAnExistingMenuCommand = false;
493 				try {
494 					if (hasColon) {
495 						theCommandIsAnExistingMenuCommand = praat_doMenuCommand (command2, narg, args, interpreter);
496 					} else {
497 						theCommandIsAnExistingMenuCommand = praat_doMenuCommand (command, arguments, interpreter);
498 					}
499 				} catch (MelderError) {
500 					/*
501 					 * We only get here if the command *was* an existing menu command.
502 					 * Anything could have gone wrong in its execution,
503 					 * but one invisible problem can be checked here.
504 					 */
505 					if (hasDots && arguments [0] != U'\0' && arguments [str32len (arguments) - 1] == U' ') {
506 						Melder_throw (U"It may be helpful to remove the trailing spaces in \"", arguments, U"\".");
507 					} else {
508 						throw;
509 					}
510 				}
511 				if (! theCommandIsAnExistingMenuCommand) {
512 					const integer length = str32len (command);
513 					if (str32nequ (command, U"ARGS ", 5)) {
514 						Melder_throw (U"Command \"ARGS\" no longer supported. Instead use \"form\" and \"endform\".");
515 					} else if (str32chr (command, U'=')) {
516 						Melder_throw (U"Command \"", command, U"\" not recognized.\n"
517 							U"Probable cause: you are trying to use a variable name that starts with a capital.");
518 					} else if (length >= 1 && Melder_isHorizontalSpace (command [length - 1])) {
519 						Melder_throw (U"Command \"", command, U"\" not available for current selection. "
520 							U"It may be helpful to remove the trailing spaces.");
521 					} else if (length >= 2 && Melder_isHorizontalSpace (command [length - 2]) && command [length - 1] == U':') {
522 						Melder_throw (U"Command \"", command, U"\" not available for current selection. "
523 							U"It may be helpful to remove the space before the colon.");
524 					} else if (str32nequ (command, U"\"ooTextFile\"", 12)) {
525 						Melder_throw (U"Command \"", command, U"\" not available for current selection. "
526 							U"It is possible that this file is not a Praat script but a Praat data file that you can open with \"Read from file...\".");
527 					} else {
528 						Melder_throw (U"Command \"", command, U"\" not available for current selection.");
529 					}
530 				}
531 			}
532 		}
533 		praat_updateSelection ();
534 	}
535 	return true;
536 }
537 
praat_executeCommandFromStandardInput(conststring32 programName)538 void praat_executeCommandFromStandardInput (conststring32 programName) {
539 	char command8 [1000];   // can be recursive
540 	/*
541 		FIXME: implement for Windows.
542 	*/
543 	for (;;) {
544 		printf ("%s > ", Melder_peek32to8 (programName));
545 		if (! fgets (command8, 999, stdin))
546 			Melder_throw (U"Cannot read input.");
547 		char *newLine = strchr (command8, '\n');
548 		if (newLine)
549 			*newLine = '\0';
550 		autostring32 command32 = Melder_8to32 (command8);
551 		try {
552 			(void) praat_executeCommand (nullptr, command32.get());
553 		} catch (MelderError) {
554 			Melder_flushError (programName, U": Command \"", Melder_peek8to32 (command8), U"\" not executed.");
555 		}
556 	}
557 }
558 
praat_executeScriptFromFile(MelderFile file,conststring32 arguments)559 void praat_executeScriptFromFile (MelderFile file, conststring32 arguments) {
560 	try {
561 		autostring32 text = MelderFile_readText (file);
562 		autoMelderFileSetDefaultDir dir (file);   // so that relative file names can be used inside the script
563 		Melder_includeIncludeFiles (& text);
564 		autoInterpreter interpreter = Interpreter_createFromEnvironment (praatP.editor);
565 		if (arguments) {
566 			Interpreter_readParameters (interpreter.get(), text.get());
567 			Interpreter_getArgumentsFromString (interpreter.get(), arguments);
568 		}
569 		Interpreter_run (interpreter.get(), text.get());
570 	} catch (MelderError) {
571 		Melder_throw (U"Script ", file, U" not completed.");
572 	}
573 }
574 
praat_executeScriptFromFileName(conststring32 fileName,integer narg,Stackel args)575 void praat_executeScriptFromFileName (conststring32 fileName, integer narg, Stackel args) {
576 	/*
577 		The argument 'fileName' is unsafe. Duplicate its contents.
578 	*/
579 	structMelderFile file { };
580 	Melder_relativePathToFile (fileName, & file);
581 	try {
582 		autostring32 text = MelderFile_readText (& file);
583 		autoMelderFileSetDefaultDir dir (& file);   // so that relative file names can be used inside the script
584 		Melder_includeIncludeFiles (& text);
585 		autoInterpreter interpreter = Interpreter_createFromEnvironment (praatP.editor);
586 		Interpreter_readParameters (interpreter.get(), text.get());
587 		Interpreter_getArgumentsFromArgs (interpreter.get(), narg, args);
588 		Interpreter_run (interpreter.get(), text.get());
589 	} catch (MelderError) {
590 		Melder_throw (U"Script ", & file, U" not completed.");   // don't refer to 'fileName', because its contents may have changed
591 	}
592 }
593 
praat_executeScriptFromFileNameWithArguments(conststring32 nameAndArguments)594 void praat_executeScriptFromFileNameWithArguments (conststring32 nameAndArguments) {
595 	char32 path [256];
596 	const char32 *p, *arguments;
597 	structMelderFile file { };
598 	/*
599 		Split into file name and arguments.
600 	*/
601 	p = nameAndArguments;
602 	while (*p == U' ' || *p == U'\t')
603 		p ++;
604 	if (*p == U'\"') {
605 		char32 *q = path;
606 		p ++;   // skip quote
607 		while (*p != U'\"' && *p != U'\0')
608 			* q ++ = * p ++;
609 		*q = U'\0';
610 		arguments = p;
611 		if (*arguments == U'\"')
612 			arguments ++;
613 		if (*arguments == U' ')
614 			arguments ++;
615 	} else {
616 		char32 *q = path;
617 		while (*p != U' ' && *p != U'\0')
618 			* q ++ = * p ++;
619 		*q = U'\0';
620 		arguments = p;
621 		if (*arguments == U' ')
622 			arguments ++;
623 	}
624 	Melder_relativePathToFile (path, & file);
625 	praat_executeScriptFromFile (& file, arguments);
626 }
627 
praatlib_executeScript(const char * text8)628 extern "C" void praatlib_executeScript (const char *text8) {
629 	try {
630 		autoInterpreter interpreter = Interpreter_create (nullptr, nullptr);
631 		autostring32 string = Melder_8to32 (text8);
632 		Interpreter_run (interpreter.get(), string.get());
633 	} catch (MelderError) {
634 		Melder_throw (U"Script not completed.");
635 	}
636 }
637 
praat_executeScriptFromText(conststring32 text)638 void praat_executeScriptFromText (conststring32 text) {
639 	try {
640 		autoInterpreter interpreter = Interpreter_create (nullptr, nullptr);
641 		autostring32 string = Melder_dup (text);   // copy, because Interpreter will change it (UGLY)
642 		Interpreter_run (interpreter.get(), string.get());
643 	} catch (MelderError) {
644 		Melder_throw (U"Script not completed.");
645 	}
646 }
647 
praat_executeScriptFromDialog(UiForm dia)648 void praat_executeScriptFromDialog (UiForm dia) {
649 	structMelderFile file { };
650 	Melder_pathToFile (dia -> scriptFilePath.get(), & file);
651 	autostring32 text = MelderFile_readText (& file);
652 	autoMelderFileSetDefaultDir dir (& file);
653 	Melder_includeIncludeFiles (& text);
654 	autoInterpreter interpreter = Interpreter_createFromEnvironment (praatP.editor);
655 	Interpreter_readParameters (interpreter.get(), text.get());
656 	Interpreter_getArgumentsFromDialog (interpreter.get(), dia);
657 	autoPraatBackground background;
658 	Interpreter_run (interpreter.get(), text.get());
659 }
660 
secondPassThroughScript(UiForm sendingForm,integer,Stackel,conststring32,Interpreter,conststring32,bool,void *)661 static void secondPassThroughScript (UiForm sendingForm, integer /* narg */, Stackel /* args */,
662 	conststring32 /* sendingString_dummy */, Interpreter /* interpreter_dummy */,
663 	conststring32 /* invokingButtonTitle */, bool /* modified */, void *)
664 {
665 	praat_executeScriptFromDialog (sendingForm);
666 }
667 
firstPassThroughScript(MelderFile file)668 static void firstPassThroughScript (MelderFile file) {
669 	try {
670 		autostring32 text = MelderFile_readText (file);
671 		{// scope
672 			autoMelderFileSetDefaultDir dir (file);
673 			Melder_includeIncludeFiles (& text);
674 		}
675 		autoInterpreter interpreter = Interpreter_createFromEnvironment (praatP.editor);
676 		if (Interpreter_readParameters (interpreter.get(), text.get()) > 0) {
677 			autoUiForm form = Interpreter_createForm (interpreter.get(),
678 				praatP.editor ? praatP.editor -> windowForm : theCurrentPraatApplication -> topShell,
679 				Melder_fileToPath (file), secondPassThroughScript, nullptr, false
680 			);
681 			UiForm_destroyWhenUnmanaged (form.get());
682 			UiForm_do (form.get(), false);
683 			form. releaseToUser();
684 		} else {
685 			autoPraatBackground background;
686 			praat_executeScriptFromFile (file, nullptr);
687 		}
688 	} catch (MelderError) {
689 		Melder_throw (U"Script ", file, U" not completed.");
690 	}
691 }
692 
fileSelectorOkCallback(UiForm dia,integer,Stackel,conststring32,Interpreter,conststring32,bool,void *)693 static void fileSelectorOkCallback (UiForm dia, integer /* narg */, Stackel /* args */,
694 	conststring32 /* sendingString_dummy */, Interpreter /* interpreter_dummy */,
695 	conststring32 /* invokingButtonTitle */, bool /* modified */, void *)
696 {
697 	firstPassThroughScript (UiFile_getFile (dia));
698 }
699 
DO_RunTheScriptFromAnyAddedMenuCommand(UiForm,integer,Stackel,conststring32 scriptPath,Interpreter,conststring32,bool,void *)700 void DO_RunTheScriptFromAnyAddedMenuCommand (UiForm /* sendingForm_dummy */, integer /* narg */, Stackel /* args */,
701 	conststring32 scriptPath, Interpreter /* interpreter */,
702 	conststring32 /* invokingButtonTitle */, bool /* modified */, void *)
703 {
704 	structMelderFile file { };
705 	Melder_relativePathToFile (scriptPath, & file);
706 	firstPassThroughScript (& file);
707 }
708 
DO_RunTheScriptFromAnyAddedEditorCommand(Editor editor,conststring32 script)709 void DO_RunTheScriptFromAnyAddedEditorCommand (Editor editor, conststring32 script) {
710 	praatP.editor = editor;
711 	DO_RunTheScriptFromAnyAddedMenuCommand (nullptr, 0, nullptr, script, nullptr, nullptr, false, nullptr);
712 	/*praatP.editor = nullptr;*/
713 }
714 
715 /* End of file praat_script.cpp */
716