-
Notifications
You must be signed in to change notification settings - Fork 8
/
matl_compile.m
546 lines (517 loc) · 30.5 KB
/
matl_compile.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
function S = matl_compile(S, F, L, pOutFile, cOutFile, verbose, isMatlab, verNum, useTags, online)
%
% MATL compiler. Compiles into MATLAB code.
%
% The main input is a struct array with parsed statements.
% Produces output file with the MATLAB code.
%
% Luis Mendo
% Each MATLAB line is a string in cell array C.
% Variables related to MATL are uppercase: STACK, S_IN, S_OUT, CB_G--CB_M.
% The MATL stack (variable 'STACK') is implemented in MATLAB as a dynamic-size cell, for two
% reasons:
% - simplicity of the code
% - that way the variable representing the stack can be viewed directly
% when debugging
%
% The multi-level clipboard L (variable 'CB_L') is a cell array of cells. The "outer" cells
% refer to clipboard levels. The "inner cells" refer to copied elements within that clipboard
% level. CB_L is a dynamic cell array: outer cells are created on the fly when clipboard levels
% are copied to.
global indStepComp C implicitInputBlock
indStepComp = 4;
thisdir = fileparts(mfilename('fullpath'));
compat_folder = fullfile(thisdir, 'compatibility');
if verbose
disp(' Generating compiled code')
end
% For replacing letters in literal arrays:
arrayReplaceOrig = {'O' 'l' 'H' 'I' 'K' 'A' 'B' 'C' 'D' 'E' 'X' 'a' 'b' 'c' 'd' 'F' 'T' 'P' 'Y' 'N' 'J' 'G'};
arrayReplaceOrigMat = cell2mat(arrayReplaceOrig); % same as a matrix. Above cells must contain only single letters
arrayReplaceDest = {'0' '1' '2' '3' '4' '5' '6' '7' '8' '9' '10' '-1' '-2' '-3' '-4' 'false' 'true' 'pi' 'inf' 'NaN' 'j' '-1j'};
% spaces in arrayReplaceDest are added later
arrayAllowMat = 'eji';
Fsource = {F.source}; % this field of `F` will be used often
% Possible improvement: preallocate for greater speed, and modify
% appendLines so that C doesn't dynamically grow
C = {}; % Cell array of strings. Each cell contains a line of compiled (MATLAB) code
if useTags
strongBegin = '<strong>';
strongEnd = '</strong>';
else
strongBegin = '';
strongEnd = '';
end
% Define blocks of code that will be reused
implicitInputBlock = {...
'if ~isempty(nin) && (numel(STACK)+nin(1)<1)' ...
'implInput = {};' ...
'for k = 1:1-numel(STACK)-nin(1)' ...
'implInput{k} = input(implicitInputPrompt,''s'');' ...
'valid = isempty(regexp(implInput{k}, ''^[^'''']*(''''[^'''']*''''[^'''']*)*[a-df-hk-zA-EG-MOQ-SU-XZ]'', ''once'')) && isempty(regexp(implInput{k}, ''^[^'''']*(''''[^'''']*''''[^'''']*)*[a-zA-Z]{2}'', ''once''));' ...
'assert(valid, ''MATL:runner'', ''MATL run-time error: input not allowed'')' ...
'if isempty(implInput{k}), implInput{end} = []; else implInput{k} = eval(implInput{k}); end' ...
'end' ...
'STACK = [implInput STACK];' ...
'CB_G = [CB_G implInput];' ...
'clear implInput k' ...
'end'};
% We don't update CB_M. This implicit input is not considered a
% function call, and would have 0 inputs anyway
% Include function header
[~, name] = fileparts(cOutFile);
appendLines(['function ' name], 0)
% Include date and time
appendLines(['% Generated by MATL compiler, ' datestr(now)], 0)
% Set initial conditions. Also, specify cleanup functions to restore initial conditions at the end
% of the program even if an error occurs. Thanks, Suever!
appendLines('', 0)
appendLines('% Set initial conditions', 0)
appendLines('tic;', 0)
appendLines('warningState = warning;', 0);
appendLines('format compact; format long; warning(''off'',''all''); close all', 0) % clc
appendLines('defaultColorMap = get(0, ''DefaultFigureColormap'');', 0)
appendLines('set(0, ''DefaultFigureColormap'', gray(256));', 0)
appendLines('cleanup_warn = onCleanup(@()warning(warningState));', 0);
appendLines('cleanup_diary = onCleanup(@()diary(''off''));', 0);
appendLines('cleanup_cmap = onCleanup(@()set(0, ''DefaultFigureColormap'', defaultColorMap));', 0);
if ~isMatlab
appendLines('page_screen_output(false, ''local'');', 0)
appendLines('page_output_immediately(true, ''local'');', 0)
% This is needed in Octave so it honours pauses etc. Thanks, Suever!
end
if isMatlab && exist('rng', 'file') % recent Matlab version
appendLines('cleanup_rng = onCleanup(@()rng(''default''));', 0);
appendLines('rng(''shuffle'')', 0)
elseif isMatlab % old Matlab version
appendLines('rand(''seed'',sum(clock)); randn(''seed'',sum(clock))', 0);
% else % Octave: seeds are set randomly automatically by Octave
end
if ~online
appendLines('diary off; delete defout; diary defout', 0)
end
% For user inputs or inputs to "str2num"
appendLines('F = false; T = true; P = pi; Y = inf; N = NaN;', 0)
% Constants to be used by function code
appendLines('numCbM = 4;', 0); % number of levels in clipboard M
if online
appendLines('defaultInputPrompt = '''';', 0);
appendLines('implicitInputPrompt = '''';', 0);
else
appendLines('defaultInputPrompt = ''> '';', 0);
appendLines('implicitInputPrompt = ''> '';', 0);
end
appendLines('replaceBySpace = char([0:7 14:31]);', 0); % these characters will be replaced by space
% Predefine literals for functions
if ~isempty(S)
plf = cellstr(char(bsxfun(@plus, 'X0', [floor(0:.1:2.9).' repmat((0:9).',3,1)]))).'; % {'X0'...'Z9'}
str = intersect(plf, {S.source}); % only those functions that are actually used
for n = 1:numel(str);
appendLines(['preLit.' str{n} '.key = ' mat2str(L.(str{n}).key) '; '], 0)
appendLines(['preLit.' str{n} '.val = {' sprintf('%s ', L.(str{n}).val{:}) '};'], 0)
end
end
% Define initial values for stack, input/output specs and clipboards:
appendLines('STACK_ini = {};',0)
appendLines('S_IN_ini = ''def''; S_OUT_ini = ''def'';', 0);
appendLines('CB_H_ini = { 2 }; CB_I_ini = { 3 }; CB_J_ini = { 1j }; CB_K_ini = { 4 }; CB_L_ini = { {[1 2 1j]} {[2 2 1j]} {[1 1j-1]} {[2 1j]} {[1 0]} {[2 1j-1]} {[2 3 1]} {[3 1 2]} {[1 1j]} {[1j -1 1]} {3600} {86400} {1440} {[31 28 31 30 31 30 31 31 30 31 30 31]} {[31 29 31 30 31 30 31 31 30 31 30 31]} {0.5772156649015328606} {(sqrt(5)+1)/2} {2j*pi} {[1 3 2 4]} {[1 3 4 2]} {[3 1 2 4]} {[3 1 4 2]} {[3 4 1 2]} {[1 .5j]} {[1 .5+.5j]} {[1+.5j 1j]} {[.5+.5j 1j]} };', 0)
appendLines('CB_G_ini = { }; CB_M_ini = repmat({{}},1,numCbM);', 0)
% Initiallize stack
appendLines('STACK = STACK_ini;', 0)
% Initiallize function input and output specifications
appendLines('S_IN = S_IN_ini; S_OUT = S_OUT_ini;', 0)
% Initiallize clipboards. Clipboards H--L are implemented directly as variables.
% Clipboard L is implemented as a cell array, where each cell is one clipboard
% "level".
appendLines('CB_H = CB_H_ini; CB_I = CB_I_ini; CB_J = CB_J_ini; CB_K = CB_K_ini; CB_L = CB_L_ini;', 0)
% Initiallize automatic clipboards. Clipboard L is implemented as a cell
% array, where each cell is one clipboard "level" containing one input. It
% is initially empty.
appendLines('CB_G = CB_G_ini; CB_M = CB_M_ini;', 0)
% Read input file, if present. Don't do it in online compiler
if ~online
appendLines('if exist(''defin'',''file''), fid = fopen(''defin'',''r''); STACK{end+1} = reshape(fread(fid,inf,''*char''),1,[]); fclose(fid); end', 0)
end
% Process each MATL statement. Precede with a commented line containing the MATL
% statement. Add a field in S indicating the line of that MATL statement in
% the compiled MATLAB file. Generate corresponding MATLAB code.
for n = 1:numel(S)
newLines = {};
appendLines('', 0)
if S(n).implicit
comment = [S(n).source ' (implicit)'];
else
comment = [S(n).source];
end
appendLines(['% ' comment], S(n).nesting) % include MATL statement as a comment
S(n).compileLine = numel(C); % take note of starting line for MATL statement in compiled code
switch S(n).type
case {'literal.number', 'literal.colonArray.numeric', 'literal.string', 'literal.colonArray.char'}
appendLines(['STACK{end+1} = ' S(n).source ';'], S(n).nesting)
case {'literal.array', 'literal.cellArray'}
x = S(n).source;
% Replace recognized letters by numbers with spaces
% for k = 1:numel(arrayReplaceOrig) % We remove this because Octave can't handle the regex (Matlab can)
% orig = ['(?<=^[^'']*(''[^'']*''[^'']*)*)' arrayReplaceOrig{k}];
% dest = [' ' arrayReplaceDest{k} ' '];
% x = regexprep(x, orig, dest);
% end
w = find(~mod(cumsum(x==''''),2) & isletter(x)); % positions of x where replacing
% should take place: letters that have an even number of preceding quotes
lastArrayAllowed = -1; % initiallize. Index of last allowed letter found
for k = w(end:-1:1) % inverse order because x will grow
[~, ind] = find(x(k)==arrayReplaceOrigMat); % x(k) is one of the letters to be replaced
if ~isempty(ind)
x = [ x(1:k-1) ' ' arrayReplaceDest{ind} ' ' x(k+1:end) ];
elseif any(x(k)==arrayAllowMat) % x(k) is allowed, as long as it's not contiguous with another
if k==lastArrayAllowed-1; % we are going backwards. Contiguous means this
error('MATL:compiler', 'Content not allowed in MATL array literal');
else
lastArrayAllowed = k; % update
end
else % equivalently: elseif ~any(x(k)==arrayAllowMat)
error('MATL:compiler', 'Content not allowed in MATL array literal');
end
% We cannot (easily) check with a regexp after letter replacing,
% because there will possibly be things like 'pi', 'true' etc.
% So we do it in the code above: an error is issued if any unrecognized letter is found,
% or if two recognized letters are contiguous
end
% Now generate compiled line
appendLines(['STACK{end+1} = ' x ';'], S(n).nesting)
case 'literal.logicalRowArray'
lit = strrep(strrep(S(n).source,'T','true,'),'F','false,');
lit = ['[' lit(1:end-1) ']'];
appendLines(['STACK{end+1} = ' lit ';'], S(n).nesting)
case 'metaFunction.inSpec'
appendLines('nin = 0;', S(n).nesting);
appendLines(implicitInputBlock, S(n).nesting); % code block for implicit input
appendLines('S_IN = STACK{end}; STACK(end) = [];', S(n).nesting)
appendLines('if isempty(S_IN), S_IN = ''def''; end', S(n).nesting)
case 'metaFunction.outSpec'
appendLines('nin = 0;', S(n).nesting);
appendLines(implicitInputBlock, S(n).nesting); % code block for implicit input
appendLines('S_OUT = STACK{end}; STACK(end) = [];', S(n).nesting)
appendLines('if isempty(S_OUT), S_OUT = ''def''; end', S(n).nesting)
case 'metaFunction.altInOut'
appendLines('S_IN = ''alt''; S_OUT = ''alt'';', S(n).nesting)
case 'controlFlow.for'
appendLines('nin = 0;', S(n).nesting);
appendLines(implicitInputBlock, S(n).nesting); % code block for implicit input
newLines{1} = sprintf('in = STACK{end}; STACK(end) = []; indFor%i = 0;', S(n).nesting);
newLines{2} = sprintf('for varFor%i = in', S(n).nesting);
appendLines(newLines, S(n).nesting)
% newLines{1} = sprintf('STACK{end+1} = varFor%i;', S(n).nesting);
newLines = sprintf('indFor%i = indFor%i+1;', S(n).nesting, S(n).nesting);
appendLines(newLines, S(n).nesting+1)
case 'controlFlow.doTwice'
newLines = sprintf('for varDoTwice%i = [0 1]', S(n).nesting);
appendLines(newLines, S(n).nesting)
% newLines{1} = sprintf('STACK{end+1} = varDoTwice%i;', S(n).nesting);
case 'controlFlow.doWhile' % '`'
newLines = { sprintf('indDoWhile%i = 0;', S(n).nesting) ...
sprintf('condDoWhile%i = true;', S(n).nesting) ...
sprintf('while condDoWhile%i', S(n).nesting) };
appendLines(newLines, S(n).nesting)
newLines = sprintf('indDoWhile%i = indDoWhile%i+1;', S(n).nesting, S(n).nesting);
appendLines(newLines, S(n).nesting+1)
case 'controlFlow.while' % 'X`'
appendLines('nin = 0;', S(n).nesting);
appendLines(implicitInputBlock, S(n).nesting); % code block for implicit input
newLines = { sprintf('indWhile%i = 0;', S(n).nesting) ...
sprintf('condWhile%i = STACK{end}; if ~isreal(condWhile%i), condWhile%i = real(condWhile%i); end', S(n).nesting, S(n).nesting, S(n).nesting, S(n).nesting) ...
'STACK(end) = [];' ...
sprintf('while condWhile%i', S(n).nesting) };
appendLines(newLines, S(n).nesting)
newLines = sprintf('indWhile%i = indWhile%i+1;', S(n).nesting, S(n).nesting);
appendLines(newLines, S(n).nesting+1)
case 'controlFlow.if' % '?'
appendLines('nin = 0;', S(n).nesting);
appendLines(implicitInputBlock, S(n).nesting); % code block for implicit input
newLines = { 'in = STACK{end}; STACK(end) = []; if ~isreal(in), in = real(in); end' ...
'if in' };
appendLines(newLines, S(n).nesting);
case 'controlFlow.else' % '}'
appendLines('else', S(n).nesting)
case 'controlFlow.finally' % '}'
if strcmp(S(S(n).from).type, 'controlFlow.doWhile')
appendLines('nin = 0;', S(n).nesting);
appendLines(implicitInputBlock, S(n).nesting); % code block for implicit input
newLines = { sprintf('condDoWhile%i = STACK{end}; if ~isreal(condDoWhile%i), condDoWhile%i = real(condDoWhile%i); end', S(n).nesting, S(n).nesting, S(n).nesting, S(n).nesting) ...
'STACK(end) = [];' ...
sprintf('if condDoWhile%i, else', S(n).nesting) }; % we use 'if condDoWhile%i, else' rather than 'if ~condDoWhile%i'
% so as to reproduce Matlab's behaviour when the condition is an array ('if ~condDoWhile%i' wouldn't do)
appendLines(newLines, S(n).nesting);
elseif strcmp(S(S(n).from).type, 'controlFlow.while')
appendLines('nin = 0;', S(n).nesting);
appendLines(implicitInputBlock, S(n).nesting); % code block for implicit input
newLines = { sprintf('condWhile%i = STACK{end}; if ~isreal(condWhile%i), condWhile%i = real(condWhile%i); end', S(n).nesting, S(n).nesting, S(n).nesting, S(n).nesting) ...
'STACK(end) = [];' ...
sprintf('if condWhile%i, else', S(n).nesting) };
appendLines(newLines, S(n).nesting);
else
error('MATL:compiler:internal', 'MATL internal error while compiling statement %s%s%s: unrecognized loop type', strongBegin, S(n).source, strongEnd)
end
case 'controlFlow.end' % ']'
if strcmp(S(S(n).from).type, 'controlFlow.doWhile')
if ~isfield(S(S(n).from), 'finally') || isempty(S(S(n).from).finally)
appendLines('nin = 0;', S(n).nesting);
appendLines(implicitInputBlock, S(n).nesting); % code block for implicit input
newLines = { sprintf('condDoWhile%i = STACK{end}; if ~isreal(condDoWhile%i), condDoWhile%i = real(condDoWhile%i); end', S(n).nesting, S(n).nesting, S(n).nesting, S(n).nesting) ...
'STACK(end) = [];' ...
'end' }; % end the "while"
appendLines(newLines, S(n).nesting);
else % the condition was already obtained at the "finally" statement
appendLines('end', S(n).nesting); % end the "if" that implements the "finally"
appendLines('end', S(n).nesting); % end the "while"
end
appendLines(sprintf('clear indDoWhile%i', S(n).nesting), S(n).nesting)
elseif strcmp(S(S(n).from).type, 'controlFlow.while')
if ~isfield(S(S(n).from), 'finally') || isempty(S(S(n).from).finally)
appendLines('nin = 0;', S(n).nesting);
appendLines(implicitInputBlock, S(n).nesting); % code block for implicit input
newLines = { sprintf('condWhile%i = STACK{end}; if ~isreal(condWhile%i), condWhile%i = real(condWhile%i); end', S(n).nesting, S(n).nesting, S(n).nesting, S(n).nesting) ...
'STACK(end) = [];' ...
'end' }; % end the "while"
appendLines(newLines, S(n).nesting);
else % the condition was already obtained at the "finally" statement
appendLines('end', S(n).nesting); % end the "if" that implements the "finally"
appendLines('end', S(n).nesting); % end the "while"
end
appendLines(sprintf('clear indWhile%i', S(n).nesting), S(n).nesting)
elseif strcmp(S(S(n).from).type, 'controlFlow.for')
newLines = { 'end' ...
sprintf('clear indFor%i', S(n).nesting) };
appendLines(newLines, S(n).nesting)
elseif strcmp(S(S(n).from).type, 'controlFlow.doTwice')
newLines = 'end';
appendLines(newLines, S(n).nesting)
elseif strcmp(S(S(n).from).type, 'controlFlow.if')
newLines = 'end';
appendLines(newLines, S(n).nesting);
else
error('MATL:compiler:internal', 'MATL internal error while compiling statement %s%s%s', strongBegin, S(n).source, strongEnd)
end
case 'controlFlow.break'
appendLines('break', S(n).nesting)
case 'controlFlow.continue'
if strcmp(S(S(n).from).type, 'controlFlow.for') || strcmp(S(S(n).from).type, 'controlFlow.doTwice')
appendLines('continue', S(n).nesting)
elseif strcmp(S(S(n).from).type, 'controlFlow.doWhile')
% evaluate loop condition before 'continue', as would be done before 'end'. The
% loop condition has nesting corresponding to the "from" statement, not to the
% current statement
newLines = { sprintf('condDoWhile%i = STACK{end}; if ~isreal(condDoWhile%i), condDoWhile%i = real(condDoWhile%i); end', S(S(n).from).nesting, S(S(n).from).nesting, S(S(n).from).nesting, S(S(n).from).nesting) ...
'STACK(end) = [];' ...
'continue' };
appendLines(newLines, S(n).nesting);
elseif strcmp(S(S(n).from).type, 'controlFlow.while')
% same as for 'doWhile' loop, just changing the root of the name of the condition variable
newLines = { sprintf('condWhile%i = STACK{end}; if ~isreal(condWhile%i), condWhile%i = real(condWhile%i); end', S(S(n).from).nesting, S(S(n).from).nesting, S(S(n).from).nesting, S(S(n).from).nesting) ...
'STACK(end) = [];' ...
'continue' };
appendLines(newLines, S(n).nesting);
else
error('MATL:compiler:internal', 'MATL internal error while compiling statement %s%s%s', strongBegin, S(n).source, strongEnd)
end
case 'controlFlow.forValue'
k = S(S(n).from).nesting;
appendLines(sprintf('STACK{end+1} = varFor%i;', k), S(n).nesting)
case 'controlFlow.doTwiceValue'
k = S(S(n).from).nesting;
appendLines(sprintf('STACK{end+1} = varDoTwice%i;', k), S(n).nesting)
case 'controlFlow.doWhileIndex'
k = S(S(n).from).nesting;
appendLines(sprintf('STACK{end+1} = indDoWhile%i;', k), S(n).nesting)
case 'controlFlow.whileIndex'
k = S(S(n).from).nesting;
appendLines(sprintf('STACK{end+1} = indWhile%i;', k), S(n).nesting)
case 'controlFlow.forIndex'
k = S(S(n).from).nesting;
appendLines(sprintf('STACK{end+1} = indFor%i;', k), S(n).nesting)
case 'function'
k = find(strcmp(S(n).source, Fsource), 1); % Fsource is guaranteed to contain unique entries.
if isempty(k)
if useTags
error('MATL:compiler', 'MATL error while compiling: function %s%s%s in <a href="matlab: opentoline(''%s'', %i)">statement number %i</a> not defined in MATL', strongBegin, S(n).source, strongEnd, pOutFile, n, n)
else
error('MATL:compiler', 'MATL error while compiling: function %s%s%s in statement number %i not defined in MATL', strongBegin, S(n).source, strongEnd, n)
end
end
if online && ~F(k).allowedOnline
error('MATL:compiler', 'MATL compiler error: function %s not allowed in online compiler', F(k).source)
end
appendLines(funWrap(F(k).minIn, F(k).maxIn, F(k).defIn, F(k).altIn, F(k).minOut, F(k).maxOut, F(k).defOut, F(k).altOut, ...
F(k).consumeInputs, F(k).wrap, F(k).funInClipboard, F(k).body), S(n).nesting)
C = [C strcat(blanks(indStepComp*S(n).nesting), newLines)];
otherwise
error('MATL:compiler:internal', 'MATL internal error while compiling statement %s%s%s: unrecognized statement type', strongBegin, S(n).source, strongEnd)
end
end
% Close function, in case there are subfunctions
appendLines('', 0)
appendLines('end', 0)
% Define subfunctions for compatibility with Octave
if ~isMatlab
appendLines('', 0)
appendLines('% Define subfunctions', 0)
appendLines('function y = e()', 0)
appendLines('error(''Number e not recognized'')', 1)
appendLines({'end' ''}, 0)
% If any of the names in `fnames` is found, the content of the file
% '..._comp.m' will be included in the compiled file. That '..._comp.m' file may
% contain one or several functions; each of those functions (even if there's only one) should
% have an 'end' statement
fnames = {'num2str' 'im2col' 'spiral' 'unique' 'union' 'intersect' 'setdiff' 'setxor' 'ismember' ...
'triu' 'tril' 'randsample' 'nchoosek' 'vpa' 'sum' 'mean' 'prod' 'diff' 'mod' 'repelem' 'dec2bin' 'dec2base' ...
'hypergeom' 'disp' 'str2func' 'logical' 'circshift' 'pdist2' 'strsplit' 'max' 'min' 'strncmp' 'round'...
'datestr' 'regexp' 'regexprep' 'imshow' 'mat2str' 'blkdiag' 'strcat' 'str2num' 'str2double' 'cconv' ...
'gcd' 'lcm' 'fftn' 'mode' 'nnz' 'str2sym' 'factor' 'bwlabeln' 'perms' 'bwselect'};
verNumTh = [4 0 0]; % first version in which a modified function is not needed:
if (verNum(1)<verNumTh(1)) || ((verNum(1)==verNumTh(1)) && (verNum(2)<verNumTh(2))) || ((verNum(1)==verNumTh(1)) && (verNum(2)==verNumTh(2)) && (verNum(3)<verNumTh(3)))
fnames = [fnames {'colon'}];
end
for n = 1:numel(fnames)
fname = fnames{n};
if any(~cellfun(@isempty,strfind(C,fname))) % This may give false positives, but that's not a problem
fid = fopen(fullfile(compat_folder, [fname '_comp.m']), 'r');
x = reshape(fread(fid,inf,'*char'),1,[]);
fclose(fid);
x = regexprep(x, '\r\n', '\n');
appendLines(x, 0)
appendLines('', 0)
end
end
end
if verbose
fprintf(' Writing to file ''%s''\n', cOutFile')
end
% Write to file:
if exist(cOutFile,'file')
delete(which(cOutFile)) % even though `fwrite` discards the file's previous contents, and there's a `clear`
% later, I delete the file initially here. I do it just in case: at some point I've
% seen Octave run an old version of the compiled file after changing the source code (which produces
% a new compiled file). I think this must a a file cache thing. (Even accidentally deleting a file
% before attemtping to run it sometimes resulted in a successful run!) I'm not sure how helpful
% deleting the file will be, though.
% Initially the line above was `delete(cOutFile)`. I changed to `delete(which(cOutFile))`
% following a conversation with Suever (https://chat.stackexchange.com/transcript/message/34122132#34122132)
end
fid = fopen(cOutFile,'w');
for n = 1:numel(C)
if ispc % Windows
linebreak = '\r\n';
elseif ismac % Mac
linebreak = '\r';
elseif isunix % Unix, Linux
linebreak = '\n';
else % others. Not sure what to use here
linebreak = '\r\n';
end
fprintf(fid, ['%s' repmat(linebreak,1,n<numel(C))], C{n}); % avoid linebreak in last line
end
fclose(fid);
% Clear file so that the new one will be used
clear(cOutFile)
end
function newLines = funPre(minIn, maxIn, defIn, altIn, minOut, maxOut, defOut, altOut, consume, funInClipboard)
% Code generated at the beginning of functions: check `S_IN` and `S_OUT`,
% define `nout`, get inputs, prepare outputs, consume inputs if applicable.
% `consume` indicates if inputs should be removed from the stack
% (1) If `nout` is a non-negative number (specified or by default), the function should
% produce that number of outputs. For MATL functions that simply call a MATLAB function,
% this implies calling the MATLAB function with that number of outputs. For MATL functions
% that are implemented "manually", it's their responsibility to produce `nout` outputs.
% If `nout` is a logical array, the number of outputs produced by the function
% should be the number of elements in the logical array. At the end (funPost), the
% outputs corresponding to false values will be discarded.
% (2) For functions for which the default value of S_OUT depends on the
% inputs (cannot be computed at compile-time) and can be computed at
% run-time at the beginning of the function, this default value is
% specified in the function definition file as the appropriate code, in the form of
% a string that will be evaluated at the beginning of the function call.
% That result from string will be assigned to `nout`.
% (3) For functions for which the default value of S_OUT depends on the inputs
% and is very difficult to compute even at run-time at the beginning of the function,
% a negative number is specified as default value in the function definition file. A negative
% number means "`nout` unspecified, the function will build the variable `out` with the default
% number of outputs as defined by the function, or with `nout` outputs if `nout` is nonnegative".
% A negative value of the default number of outputs is translated into the appropriate text
% in the help and documentation; different negative values can be used to select that text.
% If possible, it's probably safer to use method (2) than (3).
global implicitInputBlock
newLines = { sprintf('if strcmp(S_IN,''def''), S_IN = %s; end', defIn) };
if isempty(altIn) && isempty(altOut)
newLines{end+1} = 'if strcmp(S_IN,''alt''), error(''MATL:runner'', ''MATL run-time error: alternative input/output specification not defined for this function'' ), end';
elseif ~isempty(altIn)
newLines{end+1} = sprintf('if strcmp(S_IN,''alt''), S_IN = %s; end', altIn);
else
newLines{end+1} = sprintf('if strcmp(S_IN,''alt''), S_IN = %s; end', defIn);
end
newLines = [newLines, {...
sprintf('if isnumeric(S_IN) && numel(S_IN) == 1, if S_IN < %s || S_IN > %s, error(''MATL:runner'', ''MATL run-time error: incorrect input specification''), end', minIn, maxIn) ...
sprintf('elseif islogical(S_IN), if nnz(S_IN) < %s || nnz(S_IN) > %s, error(''MATL:runner'', ''MATL run-time error: incorrect input specification''), end', minIn, maxIn) ...
'else error(''MATL:runner'', ''MATL run-time error: input specification not recognized''), end' ...
'if isnumeric(S_IN), nin = -S_IN+1:0; else nin = find(S_IN)-numel(S_IN); end' }];
newLines = [newLines implicitInputBlock]; % code block for implicit input
newLines = [newLines, {'in = STACK(end+nin);'} ];
if funInClipboard
newLines = [newLines, {'if ~isempty(in), CB_M = [{in} CB_M(1:end-1)]; end'} ];
end
newLines = [newLines, { sprintf('if strcmp(S_OUT,''def''), S_OUT = %s; end', defOut) }];
if isempty(altIn) && isempty(altOut)
newLines{end+1} = 'if strcmp(S_OUT,''alt''), error(''MATL:runner'', ''MATL run-time error: alternative input/output specification not defined for this function'' ), end';
elseif ~isempty(altOut)
newLines{end+1} = sprintf('if strcmp(S_OUT,''alt''), S_OUT = %s; end', altOut);
else
newLines{end+1} = sprintf('if strcmp(S_OUT,''alt''), S_OUT = %s; end', defOut);
end
newLines = [newLines, {...
sprintf('if isnumeric(S_OUT) && numel(S_OUT) == 1, if S_OUT >= 0 && (S_OUT < %s || S_OUT > 2*(%s)), error(''MATL:runner'', ''MATL run-time error: incorrect output specification''), end', minOut, maxOut) ...
sprintf('elseif islogical(S_OUT), if numel(S_OUT) < %s || numel(S_OUT) > %s, error(''MATL:runner'', ''MATL run-time error: incorrect output specification''), end', minOut, maxOut) ...
'else error(''MATL:runner'', ''MATL run-time error: output specification not recognized''), end' ...
sprintf('if isnumeric(S_OUT) && S_OUT > %s, S_OUT = [false(1,S_OUT-(%s+1)) true]; end', maxOut, maxOut) ...
'if isnumeric(S_OUT), nout = S_OUT; else nout = numel(S_OUT); end' ...
'if nout>=0, out = cell(1,nout); end' }];
% 2*(...) because a number in maxOut+1:2*maxOut corresponds to a logical vector with a single true value
% For logical S_IN we use nnz (the inputs are picked from the stack), but
% for logical S_OUT we use numel (the function is called with that many outputs)
if consume
newLines{end+1} = 'STACK(end+nin) = [];';
end
end
function newLines = funPost
% Code generated at the end of every normal function: get outputs, push
% outputs, delete S_IN and S_OUT.
newLines = { 'if islogical(S_OUT), out = out(S_OUT); end' ...
'STACK = [STACK out];' ...
'S_IN = ''def'';' ...
'S_OUT = ''def'';' ...
'clear nin nout in out' };
end
function newLines = funWrap(minIn, maxIn, defIn, altIn, minOut, maxOut, defOut, altOut, consumeInputs, wrap, funInClipboard, body)
% Implements use of stack to get inputs and outputs and realizes function body.
% Specifically, it packs `funPre`, function body and `funPost`.
% This is used for normal, stack-rearranging and clipboard functions.
% Meta-functions don't have this; just the function body.
if funInClipboard && ~wrap
error('MATL:compiler:internal', 'MATL internal error while compiling: funInClipboard==true with wrap==false not implemented in the compiler. funInClipboard is only handled by funPre, which is only called if wrap==true')
end
if ~iscell(body)
body = {body}; % convert to 1x1 cell array containing a string
end
if wrap
newLines = [ funPre(minIn, maxIn, defIn, altIn, minOut, maxOut, defOut, altOut, consumeInputs, funInClipboard) body funPost ];
else
newLines = body;
end
end
function appendLines(newLines, nesting)
% Appends lines to cell array of MATLAB code.
% `newLines` is a string or a cell array of strings
global indStepComp
global C
if ~iscell(newLines), newLines = {newLines}; end % string: convert to 1x1 cell array containing a string
newLines = strcat({blanks(indStepComp*nesting)}, newLines);
C(end+(1:numel(newLines))) = newLines;
end