function [C,EXPORT,ERROR] = myparsecontrols(C,D,LABELS,EXPORT)
% myparsecontrols

if ~exist('D','var')
    D = struct();
end

if ~exist('LABELS','var')
    LABELS = {};
end

if ~exist('EXPORT','var')
    EXPORT = struct('filename',{},'content',{});
end

%**************************************************************************

ERROR = struct();
ERROR.code = '';
ERROR.exprsn = '';
ERROR.leftover = '';

startcontrols = xxstartcontrols();
[pos,command] = regexp(C,startcontrols,'once','start','match');
while ~isempty(pos)
    len = length(command);
    head = C(1:pos-1);
    command(1) = '';
    
    [s,tail,iserror] = feval(['xxparse',command],C,pos+len);
    if iserror
        ERROR.code = C(pos:end);
        return
    end
    
    [replace,ERROR.exprsn] = ...
        feval(['xxreplace',command],s,D,LABELS);
    if ~isempty(ERROR.exprsn)
        return
    end
    
    if strcmp(command,'export')
        EXPORT = xxexport(s,EXPORT,LABELS);
    end
            
    C = [head,replace,tail];
    [pos,command] = regexp(C,startcontrols,'once','start','match');
end

pos = regexp(C,[startcontrols,'|!do|!elseif|!else|!case|!otherwise|!end'], ...
    'once','start');
if ~isempty(pos)
    ERROR.leftover = C(pos:end);
end

end

%**************************************************************************
function C = xxstartcontrols()
    C = '!if|!for|!switch|!export';
end

%**************************************************************************
function [S,TAIL,ERROR] = xxparsefor(C,POS) %#ok<DEFNU>

S = struct();
S.FORBODY = '';
S.DOBODY = '';
S.TAIL = '';

[S.FORBODY,POS,match] = xxfindsubcontrol(C,POS,'!do');
ERROR = ~strcmp(match,'!do');
if ERROR
    return
end

[S.DOBODY,POS,match] = xxfindend(C,POS);
ERROR = ~strcmp(match,'!end');
if ERROR
    return
end

TAIL = C(POS:end);

end
% xxparserfor().

%**************************************************************************
function [S,TAIL,ERROR] = xxparseif(C,POS) %#ok<DEFNU>

S = struct();
S.IFCOND = '';
S.IFBODY = '';
S.ELSEIFCOND = {};
S.ELSEIFBODY = {};
S.ELSEBODY = '';
TAIL = '';
getcond = @(x) regexp(x,'^[^;\n]+','match','end','once');

[IF,POS,match] = xxfindsubcontrol(C,POS,{'!elseif','!else'});
ERROR = ~any(strcmp(match,{'!elseif','!else','!end'}));
if ERROR
    return
end
[S.IFCOND,finish] = getcond(IF);
S.IFBODY = IF(finish+1:end);

while strcmp(match,'!elseif')
    [ELSEIF,POS,match] = xxfindsubcontrol(C,POS,{'!elseif','!else'});
    ERROR = ~any(strcmp(match,{'!elseif','!else','!end'}));
    if ERROR
        return
    end
    [S.ELSEIFCOND{end+1},finish] = getcond(ELSEIF);
    S.ELSEIFBODY{end+1} = ELSEIF(finish+1:end);
end

if strcmp(match,'!else')
    [S.ELSEBODY,POS,match] = xxfindend(C,POS);
    ERROR = ~strcmp(match,'!end');
    if ERROR
        return
    end
end

TAIL = C(POS:end);

end
% xxparseif().

%**************************************************************************
function [S,TAIL,ERROR] = xxparseswitch(C,POS) %#ok<DEFNU>

S = struct();
S.SWITCHEXP = '';
S.CASEEXP = {};
S.CASEBODY = {};
S.OTHERWISEBODY = '';
TAIL = '';
getcond = @(x) regexp(x,'^[^;\n]+','match','end','once');

[S.SWITCHEXP,POS,match] = xxfindsubcontrol(C,POS,{'!case','!otherwise'});
ERROR = ~any(strcmp(match,{'!case','!otherwise','!end'}));
if ERROR
    return
end

while strcmp(match,'!case')
    [CASE,POS,match] = xxfindsubcontrol(C,POS,{'!case','!otherwise'});
    ERROR = ~any(strcmp(match,{'!case','!otherwise','!end'}));
    if ERROR
        return
    end
    [S.CASEEXP{end+1},finish] = getcond(CASE);
    S.CASEBODY{end+1} = CASE(finish+1:end);
end

if strcmp(match,'!otherwise')
    [S.OTHERWISEBODY,POS,match] = xxfindend(C,POS);
    ERROR = ~strcmp(match,'!end');
    if ERROR
        return
    end
end

TAIL = C(POS:end);

end
% xxparseswitch().

%**************************************************************************
function [S,TAIL,ERROR] = xxparseexport(C,POS) %#ok<DEFNU>

S = struct();
S.EXPORTNAME = '';
S.EXPORTBODY = '';
TAIL = '';

[export,POS,match] = xxfindend(C,POS);
ERROR = ~strcmp(match,'!end');
if ERROR
    return
end

name = regexp(export,'^\s*\([^\n\)]*\)','once','match');
if isempty(name)
    ERROR = ['!export ',export];
    return
end
S.EXPORTNAME = strtrim(name(2:end-1));
S.EXPORTBODY = regexprep(export,'^\s*\([^\n\)]+\)','','once');
S.EXPORTBODY = strfun.removeltel(S.EXPORTBODY);

TAIL = C(POS:end);

end
% xxparseexport().

%**************************************************************************
function [REPLACE,ERROR] = xxreplacefor(S,D,LABELS) %#ok<INUSL,DEFNU>

REPLACE = '';
ERROR = '';

forbody = S.FORBODY;
dobody = S.DOBODY;

forbody = strtrim(forbody);
forbody = regexprep(forbody,'\s+',' ');
control = regexp(forbody,'^\?[^\s=!]*','once','match');
if isempty(control)
    control = '?';
end

% Put labels back in the !for body.
forbody = xxlabelsback(forbody,LABELS);

% Remove `'name='` from `forbody` to get the RHS.
forbody = regexprep(forbody,[control,'\s*=\s*'],'');

% Expand [ ... ] and { ... }.
replacefunc = @expandsqb; %#ok<NASGU>
forbody = regexprep(forbody,'\[[^\]]*\]','${replacefunc($0)}');
% forbody = regexprep(forbody,'\{[^\]]*\}','${replacefunc($0)}');

if ~isempty(ERROR)
    return
end

% Itemize the RHS of the `forbody`.
list = regexp(forbody,'[^\s,;]+','match');

    function C1 = expandsqb(C)
        C1 = '';
        try
            x = eval(C);
            if isnumeric(x) || islogical(x)
                C1 = sprintf('%g ',x);
            elseif ischar(x)
                for ii = 1 : length(x)
                    C1 = [C1,' ',x(ii)]; %#ok<AGROW>
                end
            elseif iscell(x)
                nx = length(x);
                for ii = 1 : nx
                    if isnumeric(x{ii}) || islogical(x{ii})
                        C1 = [C1,sprintf('%g',x{ii})]; %#ok<AGROW>
                    elseif ischar(x{ii})
                        C1 = [C1,x{ii}]; %#ok<AGROW>
                    end
                    if ii < nx
                        C1 = [C1,', ']; %#ok<AGROW>
                    end
                end
            end
        catch %#ok<CTCH>
            ERROR = ['!for ',forbody];
        end
    end

lowerlist = lower(list);
upperlist = upper(list);
REPLACE = '';
br = sprintf('\n');
nlist = length(list);
for i = 1 : nlist
    template = dobody;
    % These are the preferred options.
    template = strrep(template,['!lower',control],lowerlist{i});
    template = strrep(template,['!upper',control],upperlist{i});
    % The following ones are for bkw compatibility only.
    template = strrep(template,['<lower(',control,')>'],lowerlist{i});
    template = strrep(template,['<upper(',control,')>'],upperlist{i});
    template = strrep(template,['lower(',control,')'],lowerlist{i});
    template = strrep(template,['upper(',control,')'],upperlist{i});
    template = strrep(template,['<-',control,'>'],lowerlist{i});
    template = strrep(template,['<+',control,'>'],upperlist{i});
    template = strrep(template,['<',control,'>'],list{i});
    % Finally, this is the plain control variable substitution.
    template = strrep(template,control,list{i});
    template = strfun.removeltel(template);
    REPLACE = [REPLACE,br,template]; %#ok
end

end
% xxreplacefor().

%**************************************************************************
function [REPLACE,ERROR] = xxreplaceif(S,D,LABELS) %#ok<DEFNU>

REPLACE = ''; %#ok<NASGU>
ERROR = '';
br = sprintf('\n');

value = xxeval(S.IFCOND,D,LABELS);
if value
    REPLACE = S.IFBODY;
    REPLACE = [br,REPLACE];
    REPLACE = strfun.removeltel(REPLACE);
    return
end

for i = 1 : length(S.ELSEIFCOND)
    value = xxeval(S.ELSEIFCOND{i},D,LABELS);
    if value
        REPLACE = S.ELSEIFBODY{i};
        REPLACE = [br,REPLACE];%#ok<AGROW>
        REPLACE = strfun.removeltel(REPLACE);
        return
    end
end

REPLACE = S.ELSEBODY;
REPLACE = [br,REPLACE];
REPLACE = strfun.removeltel(REPLACE);
    
end
% xxreplaceif().

%**************************************************************************
function [REPLACE,ERROR] = xxreplaceswitch(S,D,LABELS) %#ok<DEFNU>

REPLACE = ''; %#ok<NASGU>
ERROR = '';
br = sprintf('\n');

[switchexp,switchvalid] = xxeval(S.SWITCHEXP,D,LABELS);
if ~switchvalid
    REPLACE = S.OTHERWISEBODY;
    REPLACE = [br,REPLACE];
    REPLACE = strfun.removeltel(REPLACE);
    return
end

for i = 1 : length(S.CASEEXP)
    [caseexp,valid] = xxeval(S.CASEEXP{i},D,LABELS);
    if valid && isequal(switchexp,caseexp)
        REPLACE = S.CASEBODY{i};
        REPLACE = [br,REPLACE]; %#ok<AGROW>
        REPLACE = strfun.removeltel(REPLACE);
        return
    end
end

REPLACE = S.OTHERWISEBODY;
REPLACE = [br,REPLACE];
REPLACE = strfun.removeltel(REPLACE);

end
% xxreplaceswitch().

%**************************************************************************
function [REPLACE,ERROR] = xxreplaceexport(S,D,LABELS) %#ok<INUSD,DEFNU>

REPLACE = '';
ERROR = '';

end
% xxreplaceexport().

%**************************************************************************
function EXPORT = xxexport(S,EXPORT,LABELS)

if ~isfield(S,'EXPORTNAME') || isempty(S.EXPORTNAME) ...
        || ~isfield(S,'EXPORTBODY')
    return
end

S.EXPORTBODY = xxlabelsback(S.EXPORTBODY,LABELS);

EXPORT(end+1).filename = S.EXPORTNAME;
EXPORT(end).content = S.EXPORTBODY;

end
% xxreplaceexport().

%**************************************************************************
function [BODY,POS,MATCH] = xxfindsubcontrol(C,POS,SUBCONTROL)

startpos = POS;
BODY = '';
MATCH = '';
startcontrols = xxstartcontrols();
stop = false;
level = 0;
if ischar(SUBCONTROL)
    s = ['|',SUBCONTROL];
elseif iscellstr(SUBCONTROL)
    s = sprintf('|%s',SUBCONTROL{:});
end     

while ~stop
    [start,MATCH] = ...
        regexp(C(POS:end),[startcontrols,'|!end',s], ...
        'start','match','once');
    POS = POS + start - 1;
    switch MATCH
        case SUBCONTROL
            stop = level == 0;
            BODY = C(startpos:POS-1);
        case '!end'
            level = level - 1;
            if level < 0
                stop = true;
                BODY = C(startpos:POS-1);
            end
        otherwise
            if ~isempty(start)
                level = level + 1;  
            else
                stop = true;
            end
    end
    POS = POS + length(MATCH);
end

end

%**************************************************************************
function [BODY,POS,MATCH] = xxfindend(C,POS)

startpos = POS;
BODY = '';
MATCH = '';
startcontrols = xxstartcontrols();
stop = false;
level = 0;

while ~stop
    [start,MATCH] = ...
        regexp(C(POS:end),[startcontrols,'|!end'], ...
        'start','match','once');
    POS = POS + start - 1;
    switch MATCH
        case '!end'
            if level == 0
                stop = true;
                BODY = C(startpos:POS-1);
            else
                level = level - 1;
            end
        otherwise
            if ~isempty(start)
                level = level + 1;  
            else
                stop = true;
            end
    end
    POS = POS + length(MATCH);
end

end
% xxfindend().

%**************************************************************************
function [VALUE,VALID] = xxeval(EXP,D,LABELS)
% doevalexpression  Evaluate !if and !switch expressions within database.

EXP = strtrim(EXP);
EXP = strrep(EXP,'!','');

% Add `D.` to all of its fields.
if isstruct(D)
    list = fieldnames(D)';
else
    list = {};
end
for i = 1 : length(list)
    EXP = regexprep(EXP,['(?<![\.!])\<',list{i},'\>'],['?.',list{i}]);
end
EXP = strrep(EXP,'?.','D.');

% Put labels back because some of them can be strings in !if or !switch
% expressions.
if ~isempty(LABELS)
    EXP = xxlabelsback(EXP,LABELS);
end

% Evaluate the expression.
try
    VALUE = eval(EXP);
    VALID = true;
catch %#ok<CTCH>
    VALUE = false;
    VALID = false;
end

end
% xxevalexpression().

%**************************************************************************
function C = xxlabelsback(C,LABELS)
% xxlabelsback  Substitute labels back for #(NN) in a string.

if isempty(LABELS)
    return
end

    function s = replace(x)
        s = '''''';
        x = sscanf(x,'#(%g)');
        if ~isempty(x) && ~isnan(x)
            s = ['''',LABELS{x},''''];
        end
    end

% End of nested function replace().
replacefunc = @replace; %#ok<NASGU>
C = regexprep(C,'(#\(\d+\))','${replacefunc($1)}');

end
% xxlabelsback().