6

Given a file name, how can I programmatically distinguish between scripts and functions in MATLAB?

If I attempt to pass an argument to a script, I get Attempt to execute SCRIPT somescript as a function:. Is there a way to detect this without attempting to execute it?


Update: As @craq pointed out, shortly after this question was posted, there was an article about this on MATLAB Central: http://blogs.mathworks.com/loren/2013/08/26/what-kind-of-matlab-file-is-this/

Szabolcs
  • 24,728
  • 9
  • 85
  • 174
  • Won't you get an error as well if you try to pass wrong number of arguments to a function? Then, it looks like the problem you are talking about is not script specific... – Ilya Kobelevskiy Apr 09 '13 at 19:03
  • @Ilya That's a different error though. I just showed the error to point out that MATLAB can tell the difference between scripts and functions, as it reports that this thing is a script. But perhaps it only figured it out after running it, I don't know ... – Szabolcs Apr 09 '13 at 19:06
  • It trows exception with identifier MATLAB:scriptNotAFunction when that happens, so you can detect this by try-catch same as matlab, but if try works script will execute normally... – Ilya Kobelevskiy Apr 09 '13 at 19:10
  • 1
    Shortly after this question was asked there was an article on this issue on [MATLAB Central](http://blogs.mathworks.com/loren/2013/08/26/what-kind-of-matlab-file-is-this/). The comments reference this question and conclude that @YYC's answer is better. (Or the combination of YYC and Floris.) – craq Feb 18 '14 at 10:36
  • @craq Thank you for pointing this out, I didn't know about that article. – Szabolcs Feb 18 '14 at 14:27

3 Answers3

9

Didn't find a clean solution, but you can probably use try-catch (as @Ilya suggested) and nargin

EDIT - Use function to avoid some naming conflict; use exist to further classify the input (e.g. MEX-file)

function is_script = is_a_script( varargin )
% is_a_script( varargin ) returns one of the following:
%   1: if the input is a script
%   0: if the input is a function
%  -1: if the input is neither a function nor a script.

is_script = 0;
switch( exist(varargin{1}) )
    case 2
        % If the input is not a MEX or DLL or MDL or build-in or P-file or variable or class or folder,
        % then exist() returns 2
        try
            nargin(varargin{1});
        catch err
            % If nargin throws an error and the error message does not match the specific one for script, then the input is neither script nor function.
            if( strcmp( err.message, sprintf('%s is a script.',varargin{1}) ) )
                is_script = 1;
            else
                is_script = -1;
            end
        end
    case {3, 4, 5, 6} % MEX or DLL-file, MDL-file, Built-in, P-file
        % I am not familiar with DLL-file/MDL-file/P-file. I assume they are all considered as functions.
        is_script = 0;
    otherwise % Variable, Folder, Class, or other cases 
        is_script = -1;
end
YYC
  • 1,792
  • 1
  • 14
  • 19
  • I like it better than my solution. I would recommend combining the two approaches - create a `isFunction` function, but inside that use this method rather than my very clumsy one. – Floris Apr 09 '13 at 19:55
  • Looks good. Do you foresee any potential issues? It may happen that the function is defined in a MEX file and it'll say, `does not know how to answer nargin/nargout`, but that doesn't break your method. Also, why don't you compare `err.identifier` instead of `err.message`? – Szabolcs Apr 09 '13 at 21:07
  • One problem with your `catch` statement: if I use, as the variable name, `m_file_in_question.m`, the `strcmp` will return `False` as the `err.message` will still be using the "short" name (without `.m`). Instead, look for a partial match, e.g. with `strfind` (but recognize that other error messages would generate "this is a function" return value, which is not actually what you want). – Floris Apr 09 '13 at 21:18
  • possible solution to `mex` question added to my answer above... feel free to use. – Floris Apr 09 '13 at 21:32
  • @Szabolcs: I don't know if there are other potential issues, for I am not really familiar with the exception handling in MATLAB. However, I hope `exist()` can help further address some corner cases like a MEX file. – YYC Apr 10 '13 at 00:06
  • @Floris: I guess the core problem here is the potential naming conflict. Hopefully using `varargin` mitigates the problem. – YYC Apr 10 '13 at 00:08
  • Sorry, I was not clear. Actually I only need to distinguish between scripts and functions written as `.m` files. I don't need to detect MEX files as different from functions. I was just thinking about what sort of things could go wrong – Szabolcs Apr 10 '13 at 00:16
  • A very nice solution; I like how you kept improving it! too bad i cant upvote it twice. – Floris Apr 10 '13 at 03:22
  • Nice! :-) [P-files can be scripts or functions](http://www.mathworks.de/de/help/matlab/ref/pcode.html) though, so I shifted them to the top case. i.e. `case {2,6}` and `case {3, 4, 5}` – craq Feb 18 '14 at 12:49
3

If you are willing to use semi-documented features, here is something to try:

function tf = isfunction(fName)
    t = mtree(fName, '-file');
    tf = strcmp(t.root.kind, 'FUNCTION');
end

This is the same function used in MATLAB Cody and Contests to measure code length.

Amro
  • 123,847
  • 25
  • 243
  • 454
  • it builds a full parse tree. As with all things undocumented, and to quote the help: "This is an experimental program whose behavior and interface is likely to change in the future." :) – Amro May 06 '13 at 21:34
2

This is a bit of a hack, but... here is a function that will return true if the argument is a function, and false if it's not. It is possible that there are exceptions where this won't work - I look forward to comments.

EDIT - catching the case where the function is in a mex file...

function b = isFunction(fName)
% tries to determine whether the entity called 'fName'
% is a function or a script
% by looking at the file, and seeing if the first line starts with 
% the key word "function"
try
    w = which(fName);
    % test for mex file:
    mx = regexp(w, [mexext '$']);
    if numel(mx)>0, b = true; return; end

    % the correct thing to do... as shown by YYC
    % if nargin(fName) >=0, b = true; return; end

    % my original alternative:
    fid = fopen(w,'r'); % open read only
    while(~feof(fid))
        l = fgetl(fid);
        % strip everything after comment
        f = strtok(l, '%');
        g = strtok(f, ' ');
        if strcmpi(g, 'function'), b=true; break; end
        if strlen(g)>0, b=false; break; end
    end
    fclose(fid);
catch
    fprintf(1, '%s not found!\n');
    b = false;
end
Floris
  • 45,857
  • 6
  • 70
  • 122