function errorCode = mex(varargin)
%MEX Compile MEX-function. 
%   All non-source code file names passed as arguments are passed to the 
%   linker without being compiled.
% 
%   Available options on all platforms are:
%   -argcheck        Add code to check validity of input arguments to
%                    MATLAB API functions (C functions only)
%   -c               Compile only; do not link
%   -D<name>[=<def>] Define preprocessor macro name <name> [as having value
%                    <def> (assignment not available under Windows)]
%   -f <file>        Use <file> as the options file; <file> is a full path 
%                    name if not in current directory
%   -g               Build a MEX-function with debugging symbols included
%   -h[elp]          Help; print this message
%   -I<pathname>     Include <pathname> in the compiler include path
%   -O               Build an optimized MEX-function
%   -output <name>   Create MEX-function named <name> (an appropriate
%                    MEX-file extension is automatically appended)
%   -outdir <name>   Place all output files in directory <name>
%   -V4              Compile MATLAB 4 syntax MEX-file to run in the MATLAB 5
%                    environment.
%                    NOTE: MEX-files generated under MATLAB 5.* will not
%                    run under MATLAB 4.*.
%   -U<name>         Undefine preprocessor macro name <name>
%   -v               Verbose; print all compiler and linker settings
% 
%   On UNIX, the following options are available:
%   -l<file>         Link against library lib<file>
%   -L<pathname>     Include <pathname> in the list of directories to search 
%                    for libraries
%   <name>=<def>     Override options file setting for variable <name>
% 
%   On Windows, the following options are available:
%   -setup           Setup location of installed compiler.  This
%                    switch should be the only argument passed
%   @<rsp_file>      Include contents of the text file <rsp_file> as
%                    command line arguments to MEX
% 
%   MEX's execution is affected by both command-line arguments and
%   an options file.  The options file contains all compiler-specific 
%   information necessary to create a MEX-function.  The default name for this
%   options file, if none is specified with the -f option, is "mexopts.bat" 
%   (Windows), and "mexopts.sh" (UNIX).
% 
%   On UNIX, the options file is written in the Bourne shell script language.
%   The options file which occurs first in the following list is used: 
%   ./mexopts.sh, $HOME/matlab/mexopts.sh, $MATLAB/bin/mexopts.sh.  Any 
%   variable specified in the options file can be overridden at the command 
%   line by use of the <name>=<def> command line argument.  If <def> has 
%   spaces in it, then it should be wrapped in single quotes 
%   (e.g. OPTFLAGS='opt1 opt2').  The definition can rely on other variables 
%   defined in the options file; in this case the variable referenced should 
%   have a prepended "$" (e.g. OPTFLAGS='$OPTFLAGS opt2').
% 
%   On Windows, the options file is written in the Perl script language.  The
%   options file, mexopts.bat, is searched for in the following directories:
%   the current directory first, then the same directory as "mex.bat".  No
%   arguments can have an embedded equal sign ("="); thus,
%
%      -DFOO     is valid, but
%      -DFOO=BAR is not.

%   Copyright (c) 1984-98 by The MathWorks, Inc.
%   $Revision: 1.78 $

% Normal behavior for MEX is to return an error code only if one is
% requested (nargout > 0).  If, instead, it is better to have it 
% return an error code regardless of nargout, then set the global
% variable MEX_RETURN_RESULT_IN_ANS to 1.  MEX will then return its
% result such that MATLAB puts that error code into the "ans" 
% default variable.
global MEX_RETURN_RESULT_IN_ANS

c = computer;
if isunix

    mexname = get_mex_opts(varargin{:});
    
    if ~isempty(mexname)
        [loaded_m, loaded_mex] = inmem;
    
        if ~isempty(loaded_mex)
            clear_mex_file(mexname);
        end

    end
    
    args = [' "ARCH=' lower(computer) '"' sprintf(' "%s"', varargin{:})];
    [errCode, output] = unix([matlabroot '/bin/mex' args], '-echo');
    
elseif strncmp(c, 'PC', 2)
    
    [mexname, setup] = get_mex_opts(varargin{:});
    matlab_bin_location=[matlabroot '\bin'];
    if exist([matlab_bin_location '\nt\mex.bat'],'file') 
        matlab_bin_location=[matlabroot '\bin\nt'];
    end
    if (any(matlab_bin_location == ' '))
        matlab_bin_location = ['"' matlab_bin_location '"'];
    end

    if setup
        dos([matlab_bin_location '\mex.bat -setup&']);
        return;
    else
        if ~isempty(mexname)
            [loaded_m, loaded_mex] = inmem;
            if ~isempty(loaded_mex)
                clear_mex_file(mexname);
            end
        end

        % Loop over all the arguments. Put extra quotes around any that
        % contain spaces.

        for i=1:prod(size(varargin))
            if (find(varargin{i} == ' '))
                varargin{i} = [ '"' varargin{i} '"' ];
            end
        end

        % Format the mex command
        cmd = ['!' matlab_bin_location '\mex.bat -called_from_matlab -matlab "\"' matlabroot '"\" ' sprintf(' %s', varargin{:})];
        eval(cmd);

        errCode = 0; % success
    end  
elseif isvms

    if any(strcmp(varargin,'-v'))
        verbose = 1;
    else
        verbose = 0;
    end
    
    %
    % Where should mex_vms.pl come from? 
    % First check the user's path.
    % Then check matlab:[bin.<arch]
    % Then check matlab:[bin]
    %
    % Stop searching as soon as one is found.
    %
    mex_name = 'mex_vms.pl';
    mex_pl = which(mex_name);
    if ( isempty(mex_pl))
        if (exist(['matlab:[bin.' lower(c) ']' mex_name])==2)
            mex_pl = ['matlab:[bin.' lower(c) ']' mex_name];
        else
            if (exist(['matlab:[bin]' mex_name])==2)
                mex_pl = ['matlab:[bin]' mex_name];
            else
                disp( ['unable to find any mex perl script, ' mex_name]);
                disp( ['    tried which(' mex_name ')']);
                disp( ['    tried exist(matlab:[bin.' lower(c) ']' mex_name ')']);
                disp([' tried exist(matlab:[bin]' mex_name ')']);
                error([' ']);
            end
        end
    end
    
    if verbose
        disp(['perl ' mex_pl sprintf(' "%s"', varargin{:})]);
    end
    unix(['perl ' mex_pl sprintf(' "%s"', varargin{:})]);

    errCode = 0;
    
elseif ~strcmp(c(1:2), 'MA')

    error(['Unknown platform: ' c]);
    
else % Macintosh:

    errCode = mac_mex(varargin{:});

end
  
if (nargout > 0) | (~isempty(MEX_RETURN_RESULT_IN_ANS)  & ...
        MEX_RETURN_RESULT_IN_ANS)
    errorCode = errCode;
elseif (errCode ~= 0)
    error('Unable to complete successfully');
end


%%%%%%%%%%%%%%%%%%%%
%%% SUBFUNCTIONS %%%
%%%%%%%%%%%%%%%%%%%%

function result = read_response_file(filename)
%
% Read a response file (a filename that starts with '@')
% and return a cell of strings, one per entry in the response file.
% Use Perl to ensure processing of arguments is the same as mex.bat
%

matlab_bin_location=[matlabroot '\bin'];
if exist([matlab_bin_location '\nt\mex.bat'],'file') 
    matlab_bin_location=[matlabroot '\bin\nt'];
end

result = {};

cmd = ['"'matlab_bin_location '\perl" -e "' ...
    'require ''' matlab_bin_location '\\perl\\lib\\shellwords.pl'';' ...
    'open(FILE, ''' filename ''') || die ''Could not open ' filename ''';' ...
    'while (<FILE>) {$line .= $_;} ' ...
    '$line =~ s/\\/\\\\/g;' ...
    '@ARGS = &shellwords($line); ' ...
    '$\" = \"\n\";' ...
    'print \"@ARGS\";'];

[s, r] = dos(cmd);

if s == 0
    cr = sprintf('\n');
    while ~isempty(r)
        [result{end+1}, r] = strtok(r, cr);
    end
end

function [mexname, setup] = get_mex_opts(varargin)
%
% GET_MEX_OPTS gets the options from the command line.
%
% name:
% It gets the name of the destination MEX-file.  This has two
% purposes: 
%   1) All platforms need to clear the MEX-file from memory before
%      attempting the build, to avoid problems rebuilding shared
%      libraries that the OS considers "in use".
%   2) Windows MATLAB deletes the MEX-file before the build occurs.
%      It then checks to see whether the MEX-file was created so as
%      to establish error status.
%   This function returns the minimum necessary information.  Further
%   processing is done on the MEX-file name by clear_mex_file to 
%   successfully clear it.
%
% setup:
% It also returns whether or not '-setup' was passed.
%

mexname = '';
outdir = '';
setup = 0;

% First, check for and expand response files into varargin.
v = {};
for count=1:nargin
    arg = varargin{count};
    if arg(1) == '@'
        new_args = read_response_file(arg(2:end));
        v(end+1:end+length(new_args)) = new_args;          
    else
        v{end+1} = arg;
    end
end

varargin = v;
nargin = length(varargin);

count = 1;
while (count <= nargin)
    arg = varargin{count};
    if isempty(mexname) & arg(1) ~= '-' & ~any(arg=='=') & any(arg=='.')
        %
        % Source file: MEX-file will be built in current directory
        % Only the first source file matters
        %
        mexname = arg;
        fileseps = find(mexname == filesep);
        if any(fileseps)
            mexname = mexname(fileseps(end)+1:end);
        end
        mexname = strtok(mexname, '.');
    elseif strcmp(arg, '-f')
        count = count + 1;
    elseif strcmp(arg, '-output')
        count = count + 1;
        if count > length(varargin)
            error('-output switch must be followed by a filename');
        end
        mexname = varargin{count};
    elseif strcmp(arg, '-outdir')
        count = count + 1;
        if count > length(varargin)
            error('-outdir switch must be followed by a directory name');
        end
        outdir = varargin{count};
    elseif strcmp(arg, '-setup')
        setup = 1;
        break;
    end
    count = count + 1;
end

mexname = fullfile(outdir, mexname);

function clear_mex_file(basename)
%
% CLEAR_MEX_FILE Clear a MEX-file from memory.  This is a tricky
%   business and should be avoided if possible.  It takes a relative
%   or absolute filename as the MEX-file name, and the list of loaded
%   MEX-file names.
%
%   If CLEAR_MEX_FILE is unable to clear the MEX-file, it will error.
%   This can happen if the MEX-file is locked.

seps = find(basename == filesep);
if isempty(seps)
    % basename is in the current directory
    fullname = fullfile(cd,basename);
else
    % -output was used to determine the location, as well as the
    % name, of the mex file.  The easiest way to find the dir.
    % it's in is to cd to it.
    savedir = cd;
    destdir = basename(1:seps(end));
    cd(destdir);
    fullname = fullfile(cd,basename);
    cd(savedir);
end

if ~isempty(findstr(fullname, 'private'))
    % Things in private directories are represented by the full
    % path
    mexname = fullname;
else
    modifiers = find(fullname == '@');
    if any(modifiers)
        % Methods have the class directory prepended
        mexname = fullname((modifiers(end)+1):end);
        % Methods are always displayed with UNIX file
        % separators
        mexname(mexname==filesep) = '/';
    else
        % Otherwise, we just use the base name
        mexname = basename;
    end
end

clear_mex(mexname);
% Make sure that the MEX-file is cleared
[ms, mexs] = inmem;
if ~isempty(strmatch(mexname, mexs, 'exact'))
    error('Your MEX-file is locked and must be unlocked before recompiling.');
end

function clear_mex(varargin)
% This will clear a MEX-file successfully, because it has no internal
% variables.  varargin is a builtin function and is therefore not a
% valid MEX-file name.

clear(varargin{:});

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% MACINTOSH SPECIFIC SUBFUNCTIONS %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function errorCode = mac_mex(varargin)
    errCode = 0; % Success

    if any(strcmp(varargin, '-mbuild'))
        scriptname = 'mbuild';
    else
        scriptname = 'mex';
    end
    
    % Handle help first
    if nargin == 0 | any(strcmp(varargin, '-h')) | any(strcmp(varargin, '-help')) | any(strcmp(varargin, '-?'))
        h=help(scriptname);
        helpwin(h);

    % Handle -setup next
    elseif any(strcmp(varargin, '-setup'))
        do_setup(varargin{:});
    else
        % determine which mexopts file to use
        mexopts_file = get_mexopts_file(varargin{:});
        if isempty(mexopts_file)
            error(['A ' scriptname 'opts file was not found or specified.', ...
                   '  Use "' scriptname ' -setup" to configure ' scriptname ' for your compiler.']);
        end
        
        % determine first character of mexopts file.
        % first character of mexopts file determines compiler environment.  
        % '%' is for Metrowerks IDE, # is for MPW.
        fid = fopen(mexopts_file, 'rt');
        if fid == -1
            error(sprintf('Could not open %sopts file "%s".', scriptname, mexopts_file));
        end
        
        compiler_char = fread(fid, 1, 'char');
        fclose(fid);
        
        switch setstr(compiler_char)
        case '#'
            errCode = do_mpw_compile(varargin{:});
        case '%'
            errCode = do_metrowerks_compile(mexopts_file, varargin{:});
        otherwise
            error(['The first character of the ' scriptname 'opts file must be either a "%" ', ...
                   '(for Metrowerks) or a "#" (for MPW).']);
        end 
    end
    
    if (nargout > 0)
        errorCode = errCode;
    end
%% END: mac_mex %%

% Determine which mexopts file is to be used.  Search path is:
% 1. file specified with -f option at mex command line
% 2. file named "mexopts" in current directory
% 3. file named "mexopts" in <MATLAB>:extern:scripts:
%
% Return full path name of mexopts file.
function optsfile = get_mexopts_file(varargin)
    % First, look for -f command line input
    ind = find(strcmp(varargin, '-f'));
    if any(ind)
        ind = ind(end) + 1; % ind is index of argument after last -f
        if length(varargin) < ind
            warning('-f must be followed by a valid filename');
        else
            optsfile = cvt_to_fullname(varargin{ind});
            return;
        end     
    end 
    
    % if -mbuild switch has been passed, look for a file called 'mbuildopts'.
    % otherwise look for 'mexopts'.
    if any(strcmp(varargin, '-mbuild'))
        optsfilename = 'mbuildopts';
    else
        optsfilename = 'mexopts';
    end
    
    % Second, look in local directory
    optsfile = cvt_to_fullname([':' optsfilename]);
    if exist(optsfile, 'file')
        return;
    end
    
    % Finally, look in MATLAB:extern:scripts:
    ML = matlabroot;
    % Check if MATLAB root variable is overridden on command line
    ind = strmatch('MATLAB=', varargin);
    if any(ind)
        ind = ind(end);
        ML = varargin{ind}(length('MATLAB=') + 1:end);
    end
    optsfile = fullfile(cvt_to_fullname(ML), 'extern', 'scripts', optsfilename);
    if (exist(optsfile, 'file'))
        return;
    end
    
    optsfile = '';
%% END: get_mexopts_file %%

% Take a partial/relative or full path filename and return
% the corresponding full path filename.
function fullname = cvt_to_fullname(partialname)
    % partialname could be of form y, :x:y, or x:y
    if any(partialname == filesep)
        % partialname could be of form :x:y or x:y
        if partialname(1) == filesep
            % This is :x:y case, which is a relative
            % path name.
            fullname = fullfile(pwd, partialname);
        else
            % This is the x:y case, which is already in
            % full path form
            fullname = partialname;
        end
    else
        % This is the y case, which is just a base filename.
        % To make it a full path name, we prepend the current
        % directory to it.
        fullname = fullfile(pwd, partialname);
    end
%% END: cvt_to_fullname %%

% handle the "-setup" option of mex.
function do_setup(varargin)
    % Scan :extern:scripts: for options files
    compiler_list = {};
    opts_extensions = {};
    opts_environments = {};
    opts_compilerType = {};

    % if -mbuild switch has been passed, look for a file called 'mbuildopts'.
    % otherwise look for 'mexopts'.
    if any(strcmp(varargin, '-mbuild'))
        optsfilename = 'mbuildopts';
        scriptname   = 'mbuild';
    else
        optsfilename = 'mexopts';
        scriptname   = 'mex';
    end
    
    opts_files = dir(fullfile(matlabroot, 'extern', 'scripts', [optsfilename '.*']));
    % scan each option file for a line in the form
    % SETUP <Choice List Title> <environment> <mexopts extension> <compiler type>
    % Where <Choice List Title> is the text put in the list that the user
    %                           selects from (but first any underscores are converted
    %                           to spaces).
    %       <environment>       is either MPW or Metrowerks currently
    %       <optsfilename extension> is the extension of the template optsfilename file to use
    %                           (i.e. "xxx" if the file is named "mexopts.xxx")
    %       <compiler type>     is either "C" for C or "F" for Fortran  
    for ind = 1:length(opts_files)
        fid = fopen(fullfile(matlabroot, 'extern', 'scripts', opts_files(ind).name), 'rt');
        if (fid > 0)
            while 1
                line = fgetl(fid);
                if ~isstr(line), break, end
            
                if length(line) > 8
                    if strcmp(line(3:7), 'SETUP')
                        line = line(8:end);
                        [compiler_list{end + 1}, rest] = strtok(line);
                        compiler_list{end}(compiler_list{end} == '_') = ' ';
                        [opts_environments{end + 1}, rest] = strtok(rest);
                        [opts_extensions{end + 1}, rest] = strtok(rest);
                        [opts_compilerType{end + 1}, rest] = strtok(rest);
                        break;
                    end
                end
            end
            
            fclose(fid);
        end
    end

    % Allow user to choose compiler:
    [selection, ok] = listdlg('PromptString', 'Select target compiler:', ...
                              'ListString', compiler_list, ...
                              'SelectionMode', 'single', ...
                              'InitialValue', 1, ...
                              'ListSize', [160 150]);
    if ~ok
        return;
    end

    % Generate optsfilename file in MATLAB:extern:scripts:
    optsfile = fullfile(matlabroot, 'extern', 'scripts', optsfilename);
    optsfile_template = fullfile(matlabroot, 'extern', 'scripts', [optsfilename '.' opts_extensions{selection}]);
    disp(sprintf('Creating file "%s" from file "%s".', optsfile, optsfile_template));
    
    if exist(optsfile, 'file')
        q = questdlg(sprintf('File "%s" already exists.  May I overwrite it?', optsfile));
        switch q
        case 'Yes'
            delete(optsfile);
    
        case 'Cancel'
            return;
        end     
    end

    srcfid = fopen(optsfile_template, 'rt');
    if srcfid == -1
        error(sprintf('Could not open file "%s".', optsfile_template));
    end
    data = fread(srcfid, inf, 'char');
    fclose(srcfid);

    destfid = fopen(optsfile, 'wt');
    if destfid == -1
        error(sprintf('Could not open file "%s".', optsfile));
    end

    fwrite(destfid, data, 'char');
    fclose(destfid);
    
    % If an MPW compiler was selected, also see if user want to make UserStartup files
    if strcmp(opts_environments{selection}, 'MPW')
        prompt = [scriptname ' -setup can create a UserStartup¥MATLAB_MEX and a UserStartupTS¥MATLAB_MEX ', ...
              'file which will add the <MATLAB>:extern:scripts: folder to the MPW Comands variable ', ...
              'automatically when MPW and ToolServer start up.  May I do this?'];
              
        buttonname = questdlg(prompt, 'MPW UserStartup files');
        switch buttonname
        case 'Yes'
            [fname, pname] = uigetfile('*', 'Select the MPW Shell application:');
            if fname == 0
                return;
            end
        
            if exist(fullfile(pname, 'Startup Items', ''), 'dir')
                startup_file = fullfile(pname, 'Startup Items', 'UserStartup¥MATLAB_MEX');
            else
                startup_file = fullfile(pname, 'UserStartup¥MATLAB_MEX');
            end
        
            if exist(fullfile(pname, 'TS Startup Items', ''), 'dir')
                ts_startup_file = fullfile(pname, 'TS Startup Items', 'UserStartupTS¥MATLAB_MEX');
            else
                ts_startup_file = fullfile(pname, 'UserStartupTS¥MATLAB_MEX');
            end
        
            for afile = {startup_file, ts_startup_file}
                afile = char(afile);
                
                disp(sprintf('Creating file "%s".', afile))
                fid = fopen(afile, 'wt');
                if (fid == -1)
                    error(sprintf('Could not open file "%s".', afile));
                end
            
                fprintf(fid, '# Don''t alter this file; Rerunning MEX or MBUILD with -setup will overwrite it.\n\n');
                fprintf(fid, 'Set Commands "%s,{Commands}"\n', fullfile(matlabroot, 'extern', 'scripts', ''));
                fprintf(fid, 'Export Commands\n');
            
                fclose(fid);
            end
        
            disp(sprintf(['\nWarning: If ToolServer or MPW Shell are currently running,\n' ...
                          'you should restart them to have these changes take effect!\n']));            
        case 'Cancel'
            return
        end
    end

    if strcmp(optsfilename, 'mbuildopts')
        disp('MBUILD -setup complete.');
    else
        disp('MEX -setup complete.');
    end
    
%% END: do_setup %%

% Handle an MPW environment compilation:
function result = do_mpw_compile(varargin)
    % See if toolserver knows where mex script is.
    result = mpw_do_toolserver_cmd('which mex', 0, 0);
    if result ~= 0
        % didn't find "mex", try adding :extern:scripts: to commands variable
        extern_scripts = fullfile(matlabroot, 'extern', 'scripts', '');
        result = mpw_do_toolserver_cmd( ...
                    sprintf('set Commands "{Commands},%s"; export Commands', extern_scripts), 0, 0);

        if result ~= 0
            error(sprintf('Could not add "%s" to ToolServer''s {Commands} variable.', extern_scripts));
        end
    
        result = mpw_do_toolserver_cmd('which mex', 0, 0);
        if result ~= 0
            error(['Toolserver could not find the script "mex".  ' ...
                   'Use "mex -setup" to configure mex for your compiler.']);
        end
    end

    % Change MPW's current directory to MATLAB's current directory
    result = mpw_do_toolserver_cmd(sprintf('directory ''%s''', pwd), 0, 0);
    if result ~= 0
        error(sprintf('Could not change ToolServer''s current directory to "%s".', pwd));
    end

    % Execute the MPW "mex" script with same arguments as this M-function was called with.
    result = mpw_do_toolserver_cmd(['mex ' sprintf('"%s" ', varargin{:})], 1, 1);
    if result == -1
        error('A ToolServer error occurred attempting to execute the "mex" script.');
    end
%% END: do_mpw_compile %%

% Send a string to the ToolServer
function resultCode = mpw_do_toolserver_cmd(theScript, verbose, bring_to_front)
    % Execute theScript.
    % TS DoScript returns result in form "<result text>|<errCode>".
    result = applescript(fullfile(matlabroot, 'extern', 'scripts', 'TS_DoScript'), ...
                        '-useEnglish', 'theScript', ['"' strrep(theScript,'"','\"') '"'], ...
                        'bring_to_front' , sprintf('%d', bring_to_front));
    result = result(2:end-1);  % Strip off extra quotes ("s)

    % Parse result into result string and error code
    index = find(result == '|'); 
    if (isempty(index))
        % If no result code was sent back, really bad things happened with Toolserver.
        % In this case print error message despite verbose setting.
        disp(result);
        resultCode = 666;
    else
        resultCode = str2num(result(index(end) + 1:end));
        if verbose
            disp(strrep(result(1:index(end)-1), '\"', '"'));
        end
    end
%% END: mpw_do_toolserver_cmd %%

% Handle a Metrowerks environment compilation.
% varargin{1} holds the full path name of the mexopts file,
% varargin{2:end} holds the command line arguments that 
% this M-function was originally called with.
function result = do_metrowerks_compile(varargin)   
    % Fields of options structure:
    % verbose - boolean
    % argcheck - boolean
    % inline - boolean
    % compileonly - boolean
    % optsfile - string
    % cmdlinevariables - struct
    %  Fields of cmdlinevariables structure:
    %  names - cell array of strings
    %  values - cell array of strings
    % output_path - string
    % output_name - string
    % symdefs - struct
    %  Fields of symdefs structure:
    %  names - cell array of strings
    %  values - cell array of strings
    % include_dirs - cell array of strings
    % debug - boolean
    % optimize - boolean
    % mbuild - boolean
    % sourcelist - cell array of strings
    % num_user_specified_source_files - integer
    % variables - struct
    % Fields of variables structure:
    %  MATLAB - string
    %  PLATFORM - string
    %  COMPILER - string
    %  BUILDSCRIPT - string
    %  NAMESCRIPT - string
    %  ENV - string
    %  DEFPREFIX - string
    %  COMPFLAGS - string
    %  LINKFLAGS - string
    %  OPTIMFLAGS - string
    %  DEBUGFLAGS - string
    %  SRCFILES   - string
    
    % Initialize default options:
    default_options.verbose = 0;
    default_options.argcheck = 0;
    default_options.inline = 0; 
    default_options.compileonly = 0;
    default_options.cmdlinevariables.names = {};
    default_options.cmdlinevariables.values = {};
    default_options.variables.MATLAB = '';
    default_options.variables.PLATFORM = '';
    default_options.variables.COMPILER = 'CodeWarrior C';
    default_options.variables.BUILDSCRIPT = 'CW_CMEX.script';
    default_options.variables.NAMESCRIPT = 'get_CW_name';
    default_options.variables.ENV = 'Metrowerks';
    default_options.variables.DEFPREFIX = 'CodeWarrior.MEX.prefix';
    default_options.variables.COMPFLAGS = '';
    default_options.variables.LINKFLAGS = '';
    default_options.variables.OPTIMFLAGS = '';
    default_options.variables.DEBUGFLAGS = '';
    default_options.variables.SRCFILES = [matlabroot ':extern:include:mxVR.r'];
    default_options.output_path = '';
    default_options.output_name = '';
    default_options.symdefs.names = {};
    default_options.symdefs.values = {};
    default_options.include_dirs = {};
    default_options.debug = 0;
    default_options.optimize = 1;
    default_options.mbuild = 0;
    default_options.sourcelist = {};
    default_options.num_user_specified_source_files = 0;
    
    % Convert command line into an "options" structure, also using
    % default values stored in "default_options"
    options = mw_parse_inputs(default_options, varargin{2:end});

    options.optsfile = varargin{1}; 
    
    % Actually call the compiler
    result = mw_do_compile(options);
%% END: do_metrowerks_compile %%

% Compile code based on settings in origoptions.  See comments
% in do_metrowerks_compile for description of the origoptions
% structure.
function result = mw_do_compile(origoptions)
    % Make copy of original options structure so we can modify it.
    options = origoptions;
    
    % Execute mexopts file, storing results in options.variables 
    % structure.
    options = mw_do_options_file(options);
    
    % Execute command line overrides, storing results in 
    % options.variables structure.
    options = mw_do_cmdline_overrides(options);
    
    switch options.variables.PLATFORM
    case {'powerpc', '68k'}
        
        % If there are any extraneous commas in the applescript
        % lists stored in options (which would prevent them from
        % being able to be compiled into AppleScript objects), 
        % clean the lists up.
        options = mw_clean_applescript_lists(options);
        
        if options.verbose
            mw_verbose_list(options);
        end
    
        result = 0;
        
        % There are source files the user specifies, and source
        % files we add within mex.m to the list of files to compile.
        % We don't actually want to do any compiling unless the 
        % user has specified at least one source file.  By doing this,
        % we allow "mex -v" to work properly.  The verbose listing will
        % be produced by the mw_verbose_list call above, and then we'll
        % exit cleanly without doing anything else.
        if options.num_user_specified_source_files > 0
            % tweak options:
            options.include_dirs{end + 1} = fullfile(options.variables.MATLAB, 'extern', 'include', '');
            
            options.sourcelist = [options.sourcelist options.variables.SRCFILES];
            
            sim_include = fullfile(options.variables.MATLAB, 'simulink', 'include', '');
            if exist(sim_include, 'dir')
                options.include_dirs{end + 1} = sim_include;
            end
        
            if options.optimize
                options.symdefs.names{end + 1} = 'NDEBUG';
                options.symdefs.values{end + 1} = '';
            end
        
            if options.argcheck
                options.sourcelist{end + 1} = fullfile(options.variables.MATLAB, 'extern', 'src', 'mwdebug.c');
                options.symdefs.names{end + 1} = 'ARGCHECK';
                options.symdefs.values{end + 1} = '';
            end
        
             if options.inline
                options.symdefs.names{end + 1} = 'ARRAY_ACCESS_INLINING';
                options.symdefs.values{end + 1} = '';
            end
            
            % Create a prefix file
            mw_make_prefix_file(options);
                    
            % Metrowerks will save any warnings/errors to errFile, 
            % where we will retrieve them and display them to the user.
            errFile = tempname;
            % Delete any preexisting file whose name is in errFile (ya can't be too safe)
            if exist(errFile, 'file')
                delete(errFile);
            end
        
            % set up arguments to APPLESCRIPT function
            ascriptargs = mw_setup_for_applescript(options, errFile);
            
            % call APPLESCRIPT to communicate with the compiler and
            % compile the MEX-file.
            result = applescript(ascriptargs{:});
            result = str2num(result);
            
            % display any compiler warnings:
            if exist(errFile, 'file')
                fid = fopen(errFile, 'rt');
                if fid > 0
                    disp(setstr(fread(fid,inf,'uchar'))');
                    fclose(fid);
                else
                    warning(sprintf('Couldn''t open the compiler output file "%s"', errFile));
                end 
                
                delete(errFile);
            end
        end
        
    case 'fat'
        % Build 68k half first:
        options_68k = origoptions;
        options_68k.variables.PLATFORM='68k';   
        options_68k.output_name = [options_68k.output_name '.68k'];
        
        result = mw_do_compile(options_68k);

        % Now build PowerMac half (and add the 68k half as a resource
        % file that the linker will copy in):
        if result == 0
            options_ppc = origoptions;
            options_ppc.variables.PLATFORM='powerpc';
            if ~options.mbuild
                targetExtension = ['.' mexext];
            else
                targetExtension = '';
            end
            options_ppc.sourcelist{end + 1} = ...
                fullfile(options_ppc.output_path, [options_68k.output_name targetExtension]);
            
            result = mw_do_compile(options_ppc);
        end
        
    otherwise
        error(sprintf('Unknown platform specified: %s.', options.variables.PLATFORM));
    end 
%% END: mw_do_compile %%

function mw_verbose_list(options)
    disp( ' ')
    disp( '--------------------------------------------------------------------------------')
    disp(['options file    = ' options.optsfile])
    disp(['ENV             = ' options.variables.ENV])
    disp(['MATLAB          = ' options.variables.MATLAB])
    disp(['COMPILER        = ' options.variables.COMPILER])
    disp(['BUILDSCRIPT     = ' options.variables.BUILDSCRIPT])
    disp(['NAMESCRIPT      = ' options.variables.NAMESCRIPT])
    disp(['DEFPREFIX       = ' options.variables.DEFPREFIX])
    disp(['COMPFLAGS       = ' options.variables.COMPFLAGS])
    disp(['LINKFLAGS       = ' options.variables.LINKFLAGS])
    disp(['OPTIMFLAGS      = ' options.variables.OPTIMFLAGS])
    disp(['DEBUGFLAGS      = ' options.variables.DEBUGFLAGS])
    disp(['SRCFILES        = ' sprintf('%s ',options.variables.SRCFILES{:})])
    disp(['PLATFORM        = ' options.variables.PLATFORM])
    disp( '--------------------------------------------------------------------------------')
    disp( ' ')
%% END: mw_verbose_list %%

% Parse the command line options passed to this M-function into an "options"
% structure (see do_metrowerks_compile for details on this structure).
% varargin{1} has the default options settings, and varargin{2:end} contains
% the original command line options.
function options = mw_parse_inputs(varargin)
    
    options = varargin{1};
    arg   = 2;
    gflag = 0;
    oflag = 0;
    
    while arg <= nargin,
        if varargin{arg}(1) == '-'
            switch varargin{arg}(2:end)
            case 'argcheck'
                options.argcheck = 1;
                
            case 'c'
                options.compileonly = 1;
                
            case 'f'
                % Ignored here, get_mexopts_file will pick this up.
                arg = arg + 1;

            case 'g'
                gflag = 1;
                
            case 'mbuild'
                options.mbuild = 1;
                
            case 'O'
                oflag = 1;
                
            case 'output'
                arg = arg + 1;
                fname = cvt_to_fullname(varargin{arg});
                index = find(fname == filesep);
                index = index(end);
                options.output_path = fname(1:index);
                options.output_name = fname(index + 1:end);

            case 'v'
                options.verbose = 1;
                
            case 'V4'
                options.symdefs.names{end + 1} = 'V4_COMPAT';
                options.symdefs.values{end + 1} = '1';
                
            case 'inline'
                options.inline = 1;
                
            otherwise
                switch varargin{arg}(2)
                case 'D'
                    if strncmp(varargin{arg}(3:end), 'V4_COMPAT', 9)
                        error('Please use -V4 option instead of -DV4_COMPAT.');
                    elseif strncmp(varargin{arg}(3:end), 'ARRAY_ACCESS_INLINING', 21)
                        error('Please use -inline instead of -DARRAY_ACCESS_INLINING.');
                    end
                
                    [sym, def] = strtok(varargin{arg}(3:end), '=');
                    % Strip off the "=" character if it exists:
                    def = def(2:end);
                    
                    options.symdefs.names{end + 1} = sym;
                    options.symdefs.values{end + 1} = def;
                    
                case 'I'
                    options.include_dirs{end + 1} = cvt_to_fullname(varargin{arg}(3:end));
                
                otherwise
                    error(sprintf('Unknown option: %s.', varargin{arg}));
                
                end
            end

        elseif any(varargin{arg} == '=')
            [var, setting] = strtok(varargin{arg}, '=');
            if all(var >= 'A' & var <= 'Z')
                switch var
                case 'MATLAB'
                    options.variables.MATLAB = setting(2:end);
                    
                case 'PLATFORM'
                    options.variables.PLATFORM = setting(2:end);

                otherwise
                    options.cmdlinevariables.names{end + 1} = var;
                    options.cmdlinevariables.values{end + 1} = setting(2:end);
                end
                
            else
                options.sourcelist{end + 1} = cvt_to_fullname(varargin{arg});
                options.num_user_specified_source_files = options.num_user_specified_source_files + 1;
                if ~exist(options.sourcelist{end}, 'file')
                    error(sprintf('Could not find file "%s".', options.sourcelist{end}));
                end
            end
        else
            options.sourcelist{end + 1} = cvt_to_fullname(varargin{arg});
            options.num_user_specified_source_files = options.num_user_specified_source_files + 1;
            if ~exist(options.sourcelist{end}, 'file')
                error(sprintf('Could not find file "%s".', options.sourcelist{end}));
            end
        
        end
    
        arg = arg + 1;
    end

    if options.compileonly & ~isempty(options.output_name)
        if options.mbuild
            warning('-output ignored (not creating a standalone application).');
        else
            warning('-output ignored (no MEX-file being created).');
        end
    end

    % If the user didn't specify an output name with -output, but
    % did specify source files, generate the MEX-file name from the
    % first source file name up to the first '.', and postpend '.mex'
    % to that substring.
    if isempty(options.output_name) & length(options.sourcelist) > 0
        ind = find(options.sourcelist{1} == filesep);
        ind = ind(end);
        options.output_path = fullfile(pwd, '');
        options.output_name = options.sourcelist{1}(ind + 1:end);
        ind = [find(options.output_name == '.'), length(options.output_name) + 1];
        ind = ind(1);
        options.output_name = options.output_name(1:ind-1);
    end

    % Default behavior is to have optimization on, and debugging off.
    % If you specify -g at the command line, optimization is turned off, 
    %       and debugging is turned on.
    % If you specify -g and -O at the command line, optimization is turned on,
    %       and debugging is turned on (May the gods help you now).
    if gflag
        options.debug = 1;
    else
        options.debug = 0;
    end
    
    if oflag | ~gflag
        options.optimize = 1;
    else
        options.optimize = 0;
    end
%% END: mw_parse_inputs %%

% Execute the mexopts file and store the resulting
% variable definitions in options.variables structure.
function options = mw_do_options_file(oldoptions)
    options = oldoptions;
    
    % Set up local workspace with mexopts-modifiable variables:
    MATLAB      = options.variables.MATLAB;
    PLATFORM    = options.variables.PLATFORM;
    COMPILER    = options.variables.COMPILER;
    BUILDSCRIPT = options.variables.BUILDSCRIPT;
    NAMESCRIPT  = options.variables.NAMESCRIPT;
    ENV         = options.variables.ENV;
    DEFPREFIX   = options.variables.DEFPREFIX;
    COMPFLAGS   = options.variables.COMPFLAGS;
    LINKFLAGS   = options.variables.LINKFLAGS;
    OPTIMFLAGS  = options.variables.OPTIMFLAGS;
    DEBUGFLAGS  = options.variables.DEBUGFLAGS;
    SRCFILES    = options.variables.SRCFILES;

    % Read in and execute mexopts file
    fid = fopen(options.optsfile, 'rt');
    lines = fread(fid, inf, 'char');
    fclose(fid);
    
    lines = setstr(lines)';
    lines = strrep(lines, sprintf('...\n'), '');
    eval(lines, ['error(sprintf(''Unable to evaluate options file "%s".  Error was:\n%s'', ', ...
                 'options.optsfile, lasterr))']);
    
    % load options.variables up with new values
    options.variables.MATLAB      = MATLAB;
    options.variables.PLATFORM    = PLATFORM;
    options.variables.COMPILER    = COMPILER;
    options.variables.BUILDSCRIPT = BUILDSCRIPT;
    options.variables.NAMESCRIPT  = NAMESCRIPT;
    options.variables.ENV         = ENV;
    options.variables.DEFPREFIX   = DEFPREFIX;
    options.variables.COMPFLAGS   = COMPFLAGS;
    options.variables.LINKFLAGS   = LINKFLAGS;
    options.variables.OPTIMFLAGS  = OPTIMFLAGS;
    options.variables.DEBUGFLAGS  = DEBUGFLAGS; 
    options.variables.SRCFILES    = SRCFILES; 
%% END: mw_do_options_file %%

% Execute the command line options that are of the form
% VAR=VALUE, and store these new values in options.variables structure.
function options = mw_do_cmdline_overrides(oldoptions)
    options = oldoptions;
    
    % Set up local workspace with mexopts-modifiable variables:
    MATLAB      = options.variables.MATLAB;
    PLATFORM    = options.variables.PLATFORM;
    COMPILER    = options.variables.COMPILER;
    BUILDSCRIPT = options.variables.BUILDSCRIPT;
    NAMESCRIPT  = options.variables.NAMESCRIPT;
    ENV         = options.variables.ENV;
    DEFPREFIX   = options.variables.DEFPREFIX;
    COMPFLAGS   = options.variables.COMPFLAGS;
    LINKFLAGS   = options.variables.LINKFLAGS;
    OPTIMFLAGS  = options.variables.OPTIMFLAGS;
    DEBUGFLAGS  = options.variables.DEBUGFLAGS;
    SRCFILES    = options.variables.SRCFILES;
    
    for var = {'MATLAB', 'PLATFORM', 'COMPILER', 'BUILDSCRIPT', 'NAMESCRIPT', 'ENV', ...
                'DEFPREFIX', 'COMPFLAGS', 'LINKFLAGS', 'OPTIMFLAGS', 'DEBUGFLAGS', ...
                'SRCFILES'}
        var = char(var);
        ind = find(strcmp(options.cmdlinevariables.names, var));
        for i = 1:length(ind)
            evalstr = sprintf('%s=''%s'';', var, options.cmdlinevariables.values{ind(i)});
            evalstr = strrep(evalstr, ['$' var], eval(var));
            eval(evalstr, 'disp(sprintf(''Unable to evaluate "%s".'', evalstr))');
        end
    end

    % load options.variables up with new values
    options.variables.MATLAB      = MATLAB;
    options.variables.PLATFORM    = PLATFORM;
    options.variables.COMPILER    = COMPILER;
    options.variables.BUILDSCRIPT = BUILDSCRIPT;
    options.variables.NAMESCRIPT  = NAMESCRIPT;
    options.variables.ENV         = ENV;
    options.variables.DEFPREFIX   = DEFPREFIX;
    options.variables.COMPFLAGS   = COMPFLAGS;
    options.variables.LINKFLAGS   = LINKFLAGS;
    options.variables.OPTIMFLAGS  = OPTIMFLAGS;
    options.variables.DEBUGFLAGS  = DEBUGFLAGS; 
    options.variables.SRCFILES    = SRCFILES; 
%% END: mw_do_cmdline_overrides %%

% Set up a cell array that contains all the arguments to pass
% to the APPLESCRIPT function in order to compile the MEX-function.
function ascriptargs = mw_setup_for_applescript(options, errFile)
    cwName = applescript(fullfile(options.variables.MATLAB, 'extern', ...
                                    'scripts', options.variables.NAMESCRIPT), '-useEnglish');   

    ascriptargs{1} = fullfile(options.variables.MATLAB, 'extern', 'scripts', options.variables.BUILDSCRIPT);
    ascriptargs{2} = '-useEnglish';
    
    arg = 3;
    
    ascriptargs{arg} = 'SourceList'; arg = arg + 1;
    ascriptargs{arg} = mw_cvt_to_applescript_list(options.sourcelist); arg = arg + 1;   
    ascriptargs{arg} = 'IncludeList'; arg = arg + 1;
    ascriptargs{arg} = mw_cvt_to_applescript_list(options.include_dirs); arg = arg + 1; 
    ascriptargs{arg} = 'BaseName'; arg = arg + 1;
    ascriptargs{arg} = ['"' options.output_name '"']; arg = arg + 1;    
    ascriptargs{arg} = 'proj_filename'; arg = arg + 1;
    ascriptargs{arg} = ['"' fullfile(options.output_path, options.output_name) '"']; arg = arg + 1; 
    ascriptargs{arg} = 'MATLABDir'; arg = arg + 1;
    ascriptargs{arg} = ['"' options.variables.MATLAB '"']; arg = arg + 1;   
    ascriptargs{arg} = 'prefixName'; arg = arg + 1;
    ascriptargs{arg} = ['"' options.output_name '.prefix"']; arg = arg + 1; 
    ascriptargs{arg} = 'CFlags'; arg = arg + 1;
    [ascriptargs{arg}, setFlag.COMPFLAGS] = ...
        mw_cvt_flag_to_applescript(options.variables.COMPFLAGS, 1, cwName); arg = arg + 1;
    ascriptargs{arg} = 'LdFlags'; arg = arg + 1;
    [ascriptargs{arg}, setFlag.LINKFLAGS] = ...
        mw_cvt_flag_to_applescript(options.variables.LINKFLAGS, 1, cwName); arg = arg + 1;
    ascriptargs{arg} = 'OptFlags'; arg = arg + 1;
    [ascriptargs{arg}, setFlag.OPTIMFLAGS] = ...
        mw_cvt_flag_to_applescript(options.variables.OPTIMFLAGS, options.optimize, cwName); arg = arg + 1;
    ascriptargs{arg} = 'DebugFlags'; arg = arg + 1;
    [ascriptargs{arg}, setFlag.DEBUGFLAGS] = ...
        mw_cvt_flag_to_applescript(options.variables.DEBUGFLAGS, options.debug, cwName); arg = arg + 1;
    ascriptargs{arg} = 'setFlags'; arg = arg + 1;
    ascriptargs{arg} = ['{cFlag:' setFlag.COMPFLAGS ',ldFlag:' setFlag.LINKFLAGS ',optFlag:' ...
                        setFlag.OPTIMFLAGS ',debugFlag:' setFlag.DEBUGFLAGS '}']; arg = arg + 1;
    ascriptargs{arg} = 'CompileOnly'; arg = arg + 1;
    ascriptargs{arg} = num2str(options.compileonly); arg = arg + 1;

    ascriptargs{arg} = 'platform'; arg = arg + 1;
    switch options.variables.PLATFORM
    case 'powerpc'
        ascriptargs{arg} = '"PPC"';
    case '68k'
        ascriptargs{arg} = '"68k"';
    end
    arg = arg + 1;

    ascriptargs{arg} = 'messageWindowFile'; arg = arg + 1;
    ascriptargs{arg} = ['"' errFile '"']; arg = arg + 1;
    ascriptargs{arg} = 'theCompiler'; arg = arg + 1;
    ascriptargs{arg} = ['"' options.variables.COMPILER '"']; arg = arg + 1; 
%% END: mw_setup_for_applescript %%

% Make an applescript list of the form "{<cellarray{1}>, ..., <cellarray{end}>}"
function aslist = mw_cvt_to_applescript_list(cellarray)
    aslist = ['{' sprintf('"%s",', cellarray{:})];
    aslist(end) = '}';
%% END: mw_cvt_to_applescript_list %%

% Make an applescript object of the form "tell <cwName> to get {<flag>}"
% if enable is true, otherwise make an empty applescript object, i.e. "".
function [asflag, flag_in_use] = mw_cvt_flag_to_applescript(flag, enable, cwName)
    if length(flag) > 0 & enable
        asflag = sprintf('tell %s to get {%s}', cwName, flag);
        flag_in_use = 'true';
    else
        asflag = '""';
        flag_in_use = 'false';
    end
%% END: mw_cvt_flag_to_applescript %%

% Make a prefix file based on the default prefix file stored in options.variables.DEFPREFIX,
% But also catenating lines of the form '#define NAME VALUE', one for each use of the
% -DNAME=VALUE mex script option.
function mw_make_prefix_file(options)
    prefix_name = fullfile(options.output_path, [options.output_name '.prefix']);
    prefix_template = fullfile(options.variables.MATLAB, 'extern', 'include', options.variables.DEFPREFIX);
    
    % copy template to options file
    templatefid = fopen(prefix_template, 'rt');
    if templatefid == -1
        error(sprintf('Could not open template file "%s".', prefix_template));
    end

    template_data = fread(templatefid, inf, 'char');
    fclose(templatefid);

    prefixfid = fopen(prefix_name, 'wt');
    if prefixfid == -1
        error(sprintf('Could not open prefix file "%s" for writing.', prefix_name));
    end

    fwrite(prefixfid, template_data, 'char');
    
    % Append command line definitions
    for count = 1:length(options.symdefs.names)
        fprintf(prefixfid, '#define %s %s\n', options.symdefs.names{count}, options.symdefs.values{count});
    end

    fclose(prefixfid);
%% END: mw_make_prefix_file %%

% If there are any extraneous commas in the applescript
% list "in" (which would prevent it from
% being able to be compiled into AppleScript objects), 
% clean it up.
function out = mw_clean_applescript_list(in)
    
    rest = in;
    out  = '';
    
    while ~isempty(rest)
        [tok, rest] = strtok(rest, ',');
        
        tok = deblank(tok);
        
        if ~isempty(tok)
            out = [out tok ','];
        end
    end

    if ~isempty(out)
        out = out(1:end-1);
    end
%% END: mw_clean_applescript_list %%    

% If there are any extraneous commas in the applescript
% lists stored in options (which would prevent them from
% being able to be compiled into AppleScript objects), 
% clean the lists up.
function options = mw_clean_applescript_lists(oldoptions)
    options = oldoptions;
    tempvar = [];
    
    for flag = {'COMPFLAGS', 'LINKFLAGS', 'OPTIMFLAGS', 'DEBUGFLAGS'}
        flag = char(flag);
        
        eval(['tempvar = options.variables.' flag ';']);
        tempvar = mw_clean_applescript_list(tempvar);
        
        eval(['options.variables.' flag ' = tempvar;']);
    end
%% END: mw_clean_applescript_lists %%

