From 013124eedf0f44e8f4c741cfae753d6044fda8b7 Mon Sep 17 00:00:00 2001 From: Houtan Bastani Date: Thu, 10 Jan 2019 14:53:07 +0100 Subject: [PATCH] ols: move parsing to a helper function --- matlab/ols/dyn_ols.m | 294 ++---------------------- matlab/ols/parse_ols_style_equation.m | 310 ++++++++++++++++++++++++++ 2 files changed, 325 insertions(+), 279 deletions(-) create mode 100644 matlab/ols/parse_ols_style_equation.m diff --git a/matlab/ols/dyn_ols.m b/matlab/ols/dyn_ols.m index 0e23b3346..6121e25ed 100644 --- a/matlab/ols/dyn_ols.m +++ b/matlab/ols/dyn_ols.m @@ -22,7 +22,7 @@ function varargout = dyn_ols(ds, fitted_names_dict, eqtags) % SPECIAL REQUIREMENTS % none -% Copyright (C) 2017-2018 Dynare Team +% Copyright (C) 2017-2019 Dynare Team % % This file is part of Dynare. % @@ -66,127 +66,20 @@ elseif nargin == 3 jsonmodel = getEquationsByTags(jsonmodel, 'name', eqtags); end -%% Estimation +%% Check to see if called from Gibbs +st = dbstack(1); +varargout = cell(1, 1); +called_from_olsgibbs = false; +if strcmp(st(1).name, 'olsgibbs') + varargout = cell(1, 4); + called_from_olsgibbs = true; + assert(length(ast) == 1); +end +%% Loop over equations for i = 1:length(ast) - if ~strcmp(ast{i}.AST.node_type, 'BinaryOpNode') ... - || ~strcmp(ast{i}.AST.op, '=') - ols_error('expecting equation with equal sign', line); - end - - % Check LHS - if ~strcmp(ast{i}.AST.arg1.node_type, 'VariableNode') ... - && ~strcmp(ast{i}.AST.arg1.node_type, 'UnaryOpNode') - ols_error('expecting Variable or UnaryOp on LHS', line); - else - if strcmp(ast{i}.AST.arg1.node_type, 'VariableNode') ... - && (ast{i}.AST.arg1.lag ~= 0 ... - || ~any(strcmp(ds.name, ast{i}.AST.arg1.name))) - ols_error('the lhs of the equation must be an Variable or UnaryOp with lag == 0 that exists in the dataset', line); - end - if strcmp(ast{i}.AST.arg1.node_type, 'UnaryOpNode') ... - && (ast{i}.AST.arg1.arg.lag ~= 0 ... - || ~any(strcmp(ds.name, ast{i}.AST.arg1.arg.name))) - ols_error('the lhs of the equation must be an Variable or UnaryOp with lag == 0 that exists in the dataset', line); - end - end - - % Set LHS (Y) - lhssub = dseries(); - Y = evalNode(ds, ast{i}.AST.arg1, jsonmodel{i}.line, dseries()); - - % Set RHS (X) - plus_node = ast{i}.AST.arg2; - last_node_to_parse = []; - residual = ''; - X = dseries(); - while ~isempty(plus_node) || ~isempty(last_node_to_parse) - Xtmp = dseries(); - if isempty(last_node_to_parse) - [plus_node, node_to_parse, last_node_to_parse] = findNextplus_node(plus_node, jsonmodel{i}.line); - else - node_to_parse = last_node_to_parse; - last_node_to_parse = []; - end - if strcmp(node_to_parse.node_type, 'VariableNode') || strcmp(node_to_parse.node_type, 'UnaryOpNode') - if strcmp(node_to_parse.node_type, 'VariableNode') - if strcmp(node_to_parse.type, 'parameter') - % Intercept - Xtmp = dseries(ones(ds.nobs, 1), ds.firstdate, node_to_parse.name); - elseif strcmp(node_to_parse.type, 'exogenous') && ~any(strcmp(ds.name, node_to_parse.name)) - % Residual if not contained in ds - if isempty(residual) - residual = node_to_parse.name; - else - ols_error(['only one residual allowed per equation; encountered ' residual ' & ' node_to_parse.name], jsonmodel{i}.line); - end - elseif strcmp(node_to_parse.type, 'endogenous') ... - || (strcmp(node_to_parse.type, 'exogenous') && any(strcmp(ds.name, node_to_parse.name))) - % Subtract VariableNode from LHS - % NB: treat exogenous that exist in ds as endogenous - lhssub = lhssub + evalNode(ds, node_to_parse, jsonmodel{i}.line, dseries()); - else - ols_error('unexpected variable type found', jsonmodel{i}.line); - end - else - % Subtract UnaryOpNode from LHS - % NB: treat exogenous that exist in ds as endogenous - lhssub = lhssub + evalNode(ds, node_to_parse, jsonmodel{i}.line, dseries()); - end - elseif strcmp(node_to_parse.node_type, 'BinaryOpNode') && strcmp(node_to_parse.op, '*') - % Parse param_expr * endog_expr - Xtmp = parseTimesNode(ds, node_to_parse, jsonmodel{i}.line); - if length(Xtmp.name) > 1 - % Handle constraits - % Look through Xtmp names for constant - % if found, subtract from LHS - to_remove = []; - for j = 1:length(Xtmp.name) - if ~isnan(str2double(Xtmp.name{j})) - lhssub = lhssub + str2double(Xtmp.name{j}) * Xtmp{j}; - to_remove = [to_remove j]; - end - % Multiply by -1 now so that it can be added together below - % Otherwise, it would matter which was encountered first, - % a parameter on its own or a linear constraint - Xtmp.(Xtmp.name{j}) = -1 * Xtmp{j}; - end - for j = length(to_remove):-1:1 - Xtmp = Xtmp.remove(Xtmp.name{j}); - end - end - else - ols_error('didn''t expect to arrive here', jsonmodel{i}.line); - end - if ~isempty(Xtmp) - to_remove = []; - for j = 1:length(Xtmp.name) - % Handle constraits - idx = find(strcmp(X.name, Xtmp.name{j})); - if ~isempty(idx) - X.(X.name{idx}) = X{idx} + Xtmp{j}; - to_remove = [to_remove j]; - end - end - for j = length(to_remove):-1:1 - Xtmp = Xtmp.remove(Xtmp.name{j}); - end - X = [X Xtmp]; - end - end - - clear residual Xtmp plus_node last_node_to_parse to_remove - - Y = Y - lhssub; - - %% Check to see if called from Gibbs - st = dbstack(1); - varargout = cell(1, 1); - called_from_olsgibbs = false; - if strcmp(st(1).name, 'olsgibbs') - varargout = cell(1, 4); - called_from_olsgibbs = true; - end + %% Parse equation i + [Y, lhssub, X] = parse_ols_style_equation(ds, ast{i}, jsonmodel{i}.line); %% Set dates fp = max(Y.firstobservedperiod, X.firstobservedperiod); @@ -328,165 +221,8 @@ for i = 1:length(ast) {'Estimates','t-statistic','Std. Error'}, 4, ... [oo_.ols.(tag).beta oo_.ols.(tag).tstat oo_.ols.(tag).stderr]); end - - if ~called_from_olsgibbs - varargout{1} = ds; - end -end end -%% Helper Functions -function ols_error(msg, line) -error(['ERROR encountered in `dyn_ols` line ' num2str(line) ': ' msg]) -end - -function [next_plus_node, node_to_parse, last_node_to_parse] = findNextplus_node(plus_node, line) -% Given an additive entry in the AST, find the next additive entry -% (next_plus_node). Also find the node that will be parsed into -% parameter*endogenous||param||exog|endog (node_to_parse). -% Function used for moving through the AST. -if ~(strcmp(plus_node.node_type, 'BinaryOpNode') && strcmp(plus_node.op, '+')) - ols_error('pairs of nodes must be separated additively', line); -end -next_plus_node = []; -last_node_to_parse = []; -if strcmp(plus_node.arg1.node_type, 'BinaryOpNode') && strcmp(plus_node.arg1.op, '+') - next_plus_node = plus_node.arg1; - node_to_parse = getOlsNode(plus_node.arg2, line); -elseif strcmp(plus_node.arg2.node_type, 'BinaryOpNode') && strcmp(plus_node.arg2.op, '+') - next_plus_node = plus_node.arg2; - node_to_parse = getOlsNode(plus_node.arg1, line); -else - node_to_parse = getOlsNode(plus_node.arg1, line); - last_node_to_parse = getOlsNode(plus_node.arg2, line); -end -end - -function node_to_parse = getOlsNode(node, line) -if ~(strcmp(node.node_type, 'BinaryOpNode') && strcmp(node.op, '*')) ... - && ~strcmp(node.node_type, 'VariableNode') ... - && ~strcmp(node.node_type, 'UnaryOpNode') - ols_error('couldn''t find node to parse', line); -end -node_to_parse = node; -end - -function X = parseTimesNode(ds, node, line) -% Separate the parameter expression from the endogenous expression -assert(strcmp(node.node_type, 'BinaryOpNode') && strcmp(node.op, '*')) -[param, X] = parseTimesNodeHelper(ds, node.arg1, line, {}, dseries()); -[param, X] = parseTimesNodeHelper(ds, node.arg2, line, param, X); -X = X.rename(param{1}); -for ii = 2:length(param) - X = [X dseries(X{1}.data, X{1}.firstdate, param{ii})]; -end -end - -function [param, X] = parseTimesNodeHelper(ds, node, line, param, X) -if isOlsParamExpr(node, line) - param = assignParam(param, node, line); -elseif isOlsVarExpr(node, line) - if isempty(X) - X = evalNode(ds, node, line, X); - else - ols_error(['got endog * endog' node.name ' (' node.type ')'], line); - end -else - ols_error('unexpected expression', line); -end -end - -function param = assignParam(param, node, line) -if ~isempty(param) - ols_error(['got param * param' node.name ' (' node.type ')'], line); -end -param = assignParamHelper(param, node, line); -end - -function param = assignParamHelper(param, node, line) -if strcmp(node.node_type, 'NumConstNode') - param{end+1} = num2str(node.value); -elseif strcmp(node.node_type, 'VariableNode') - param{end+1} = node.name; -elseif strcmp(node.node_type, 'BinaryOpNode') - if ~strcmp(node.op, '-') - ols_error(['got unexpected parameter op ' node.op], line); - end - param = assignParamHelper(param, node.arg1, line); - param = assignParamHelper(param, node.arg2, line); -else - ols_error(['got unexpected node (' node.type ')'], line); -end -end - -function tf = isOlsVar(node) -if strcmp(node.node_type, 'VariableNode') ... - && (strcmp(node.type, 'endogenous') ... - || (strcmp(node.type, 'exogenous') && any(strcmp(ds.name, node.name)))) - tf = true; -elseif strcmp(node.node_type, 'UnaryOpNode') ... - && (strcmp(node.arg.type, 'endogenous') ... - || (strcmp(node.arg.type, 'exogenous') && any(strcmp(ds.name, node.arg.name)))) - tf = true; -else - tf = false; -end -end - -function tf = isOlsVarExpr(node, line) -if strcmp(node.node_type, 'VariableNode') || strcmp(node.node_type, 'UnaryOpNode') - tf = isOlsVar(node); -elseif strcmp(node.node_type, 'BinaryOpNode') - tf = isOlsVarExpr(node.arg1, line) || isOlsVarExpr(node.arg2, line); -else - ols_error(['got unexpected type ' node.node_type], line); -end -end - -function X = evalNode(ds, node, line, X) -if strcmp(node.node_type, 'NumConstNode') - X = dseries(str2double(node.value), ds.dates, 'const'); -elseif strcmp(node.node_type, 'VariableNode') - if ~(strcmp(node.type, 'endogenous') ... - || (strcmp(node.type, 'exogenous') && any(strcmp(ds.name, node.name)))) - ols_error(['got unexpected type ' node.name ': ' node.type], line); - end - X = ds.(node.name)(node.lag); -elseif strcmp(node.node_type, 'UnaryOpNode') - Xtmp = evalNode(ds, node.arg, line, X); - % Only works if dseries supports . notation for unary op (true for log/diff) - % Otherwise, use: X = eval([node.op '(Xtmp)']); - X = Xtmp.(node.op); -elseif strcmp(node.node_type, 'BinaryOpNode') - Xtmp1 = evalNode(ds, node.arg1, line, X); - Xtmp2 = evalNode(ds, node.arg2, line, X); - X = X + eval(['Xtmp1 ' node.op ' Xtmp2']); -else - ols_error(['got unexpected node type ' node.node_type], line); -end -end - -function tf = isOlsParam(node) -if strcmp(node.node_type, 'VariableNode') && strcmp(node.type, 'parameter') - tf = true; -else - tf = false; -end -end - -function tf = isOlsParamExpr(node, line) -if strcmp(node.node_type, 'NumConstNode') - tf = true; -elseif strcmp(node.node_type, 'VariableNode') - tf = isOlsParam(node); -elseif strcmp(node.node_type, 'UnaryOpNode') - tf = false; -elseif strcmp(node.node_type, 'BinaryOpNode') - tf = isOlsParamExpr(node.arg1) && isOlsParamExpr(node.arg2); - if tf && ~strcmp(node.op, '-') - ols_error(['got unexpected op ' node.op], line); - end -else - ols_error(['got unexpected type ' node.node_type], line); -end +%% Set return value +varargout{1} = ds; end diff --git a/matlab/ols/parse_ols_style_equation.m b/matlab/ols/parse_ols_style_equation.m new file mode 100644 index 000000000..73e8e0e1d --- /dev/null +++ b/matlab/ols/parse_ols_style_equation.m @@ -0,0 +1,310 @@ +function [Y, lhssub, X] = parse_ols_style_equation(ds, ast, line) +%function X = parse_ols_style_equation() +% Run OLS on chosen model equations; unlike olseqs, allow for time t +% endogenous variables on LHS +% +% INPUTS +% ds [dseries] data +% ast [struct] AST representing the equation to be parsed +% line [int] equation line number +% +% OUTPUTS +% Y [dseries] LHS of the equation +% lhssub [dseries] RHS subtracted from LHS +% X [dseries] RHS of the equation +% +% SPECIAL REQUIREMENTS +% none + +% Copyright (C) 2019 Dynare Team +% +% This file is part of Dynare. +% +% Dynare is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% Dynare is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with Dynare. If not, see . + +if nargin ~= 3 + error('parse_ols_style_equation takes 3 arguments') +end + +if isempty(ds) || ~isdseries(ds) + error('parse_ols_style_equation: arg 1 must be a dseries'); +end + +if ~isstruct(ast) || length(ast) ~= 1 + error('ast must be a celength must be equal to 1'); +end + +if ~strcmp(ast.AST.node_type, 'BinaryOpNode') ... + || ~strcmp(ast.AST.op, '=') + ols_error('expecting equation with equal sign', line); +end + +% Check LHS +if ~strcmp(ast.AST.arg1.node_type, 'VariableNode') ... + && ~strcmp(ast.AST.arg1.node_type, 'UnaryOpNode') + ols_error('expecting Variable or UnaryOp on LHS', line); +else + if strcmp(ast.AST.arg1.node_type, 'VariableNode') ... + && (ast.AST.arg1.lag ~= 0 ... + || ~any(strcmp(ds.name, ast.AST.arg1.name))) + ols_error('the lhs of the equation must be an Variable or UnaryOp with lag == 0 that exists in the dataset', line); + end + if strcmp(ast.AST.arg1.node_type, 'UnaryOpNode') ... + && (ast.AST.arg1.arg.lag ~= 0 ... + || ~any(strcmp(ds.name, ast.AST.arg1.arg.name))) + ols_error('the lhs of the equation must be an Variable or UnaryOp with lag == 0 that exists in the dataset', line); + end +end + +% Set LHS (Y) +lhssub = dseries(); +Y = evalNode(ds, ast.AST.arg1, line, dseries()); + +% Set RHS (X) +plus_node = ast.AST.arg2; +last_node_to_parse = []; +residual = ''; +X = dseries(); +while ~isempty(plus_node) || ~isempty(last_node_to_parse) + Xtmp = dseries(); + if isempty(last_node_to_parse) + [plus_node, node_to_parse, last_node_to_parse] = findNextplus_node(plus_node, line); + else + node_to_parse = last_node_to_parse; + last_node_to_parse = []; + end + if strcmp(node_to_parse.node_type, 'VariableNode') || strcmp(node_to_parse.node_type, 'UnaryOpNode') + if strcmp(node_to_parse.node_type, 'VariableNode') + if strcmp(node_to_parse.type, 'parameter') + % Intercept + Xtmp = dseries(ones(ds.nobs, 1), ds.firstdate, node_to_parse.name); + elseif strcmp(node_to_parse.type, 'exogenous') && ~any(strcmp(ds.name, node_to_parse.name)) + % Residual if not contained in ds + if isempty(residual) + residual = node_to_parse.name; + else + ols_error(['only one residual allowed per equation; encountered ' residual ' & ' node_to_parse.name], line); + end + elseif strcmp(node_to_parse.type, 'endogenous') ... + || (strcmp(node_to_parse.type, 'exogenous') && any(strcmp(ds.name, node_to_parse.name))) + % Subtract VariableNode from LHS + % NB: treat exogenous that exist in ds as endogenous + lhssub = lhssub + evalNode(ds, node_to_parse, line, dseries()); + else + ols_error('unexpected variable type found', line); + end + else + % Subtract UnaryOpNode from LHS + % NB: treat exogenous that exist in ds as endogenous + lhssub = lhssub + evalNode(ds, node_to_parse, line, dseries()); + end + elseif strcmp(node_to_parse.node_type, 'BinaryOpNode') && strcmp(node_to_parse.op, '*') + % Parse param_expr * endog_expr + Xtmp = parseTimesNode(ds, node_to_parse, line); + if length(Xtmp.name) > 1 + % Handle constraits + % Look through Xtmp names for constant + % if found, subtract from LHS + to_remove = []; + for j = 1:length(Xtmp.name) + if ~isnan(str2double(Xtmp.name{j})) + lhssub = lhssub + str2double(Xtmp.name{j}) * Xtmp{j}; + to_remove = [to_remove j]; + end + % Multiply by -1 now so that it can be added together below + % Otherwise, it would matter which was encountered first, + % a parameter on its own or a linear constraint + Xtmp.(Xtmp.name{j}) = -1 * Xtmp{j}; + end + for j = length(to_remove):-1:1 + Xtmp = Xtmp.remove(Xtmp.name{j}); + end + end + else + ols_error('didn''t expect to arrive here', line); + end + if ~isempty(Xtmp) + to_remove = []; + for j = 1:length(Xtmp.name) + % Handle constraits + idx = find(strcmp(X.name, Xtmp.name{j})); + if ~isempty(idx) + X.(X.name{idx}) = X{idx} + Xtmp{j}; + to_remove = [to_remove j]; + end + end + for j = length(to_remove):-1:1 + Xtmp = Xtmp.remove(Xtmp.name{j}); + end + X = [X Xtmp]; + end +end +Y = Y - lhssub; +end + +%% Helper Functions +function ols_error(msg, line) +error(['ERROR encountered in `dyn_ols` line ' num2str(line) ': ' msg]) +end + +function [next_plus_node, node_to_parse, last_node_to_parse] = findNextplus_node(plus_node, line) +% Given an additive entry in the AST, find the next additive entry +% (next_plus_node). Also find the node that will be parsed into +% parameter*endogenous||param||exog|endog (node_to_parse). +% Function used for moving through the AST. +if ~(strcmp(plus_node.node_type, 'BinaryOpNode') && strcmp(plus_node.op, '+')) + ols_error('pairs of nodes must be separated additively', line); +end +next_plus_node = []; +last_node_to_parse = []; +if strcmp(plus_node.arg1.node_type, 'BinaryOpNode') && strcmp(plus_node.arg1.op, '+') + next_plus_node = plus_node.arg1; + node_to_parse = getOlsNode(plus_node.arg2, line); +elseif strcmp(plus_node.arg2.node_type, 'BinaryOpNode') && strcmp(plus_node.arg2.op, '+') + next_plus_node = plus_node.arg2; + node_to_parse = getOlsNode(plus_node.arg1, line); +else + node_to_parse = getOlsNode(plus_node.arg1, line); + last_node_to_parse = getOlsNode(plus_node.arg2, line); +end +end + +function node_to_parse = getOlsNode(node, line) +if ~(strcmp(node.node_type, 'BinaryOpNode') && strcmp(node.op, '*')) ... + && ~strcmp(node.node_type, 'VariableNode') ... + && ~strcmp(node.node_type, 'UnaryOpNode') + ols_error('couldn''t find node to parse', line); +end +node_to_parse = node; +end + +function X = parseTimesNode(ds, node, line) +% Separate the parameter expression from the endogenous expression +assert(strcmp(node.node_type, 'BinaryOpNode') && strcmp(node.op, '*')) +[param, X] = parseTimesNodeHelper(ds, node.arg1, line, {}, dseries()); +[param, X] = parseTimesNodeHelper(ds, node.arg2, line, param, X); +X = X.rename(param{1}); +for ii = 2:length(param) + X = [X dseries(X{1}.data, X{1}.firstdate, param{ii})]; +end +end + +function [param, X] = parseTimesNodeHelper(ds, node, line, param, X) +if isOlsParamExpr(node, line) + param = assignParam(param, node, line); +elseif isOlsVarExpr(node, line) + if isempty(X) + X = evalNode(ds, node, line, X); + else + ols_error(['got endog * endog' node.name ' (' node.type ')'], line); + end +else + ols_error('unexpected expression', line); +end +end + +function param = assignParam(param, node, line) +if ~isempty(param) + ols_error(['got param * param' node.name ' (' node.type ')'], line); +end +param = assignParamHelper(param, node, line); +end + +function param = assignParamHelper(param, node, line) +if strcmp(node.node_type, 'NumConstNode') + param{end+1} = num2str(node.value); +elseif strcmp(node.node_type, 'VariableNode') + param{end+1} = node.name; +elseif strcmp(node.node_type, 'BinaryOpNode') + if ~strcmp(node.op, '-') + ols_error(['got unexpected parameter op ' node.op], line); + end + param = assignParamHelper(param, node.arg1, line); + param = assignParamHelper(param, node.arg2, line); +else + ols_error(['got unexpected node (' node.type ')'], line); +end +end + +function tf = isOlsVar(node) +if strcmp(node.node_type, 'VariableNode') ... + && (strcmp(node.type, 'endogenous') ... + || (strcmp(node.type, 'exogenous') && any(strcmp(ds.name, node.name)))) + tf = true; +elseif strcmp(node.node_type, 'UnaryOpNode') ... + && (strcmp(node.arg.type, 'endogenous') ... + || (strcmp(node.arg.type, 'exogenous') && any(strcmp(ds.name, node.arg.name)))) + tf = true; +else + tf = false; +end +end + +function tf = isOlsVarExpr(node, line) +if strcmp(node.node_type, 'VariableNode') || strcmp(node.node_type, 'UnaryOpNode') + tf = isOlsVar(node); +elseif strcmp(node.node_type, 'BinaryOpNode') + tf = isOlsVarExpr(node.arg1, line) || isOlsVarExpr(node.arg2, line); +else + ols_error(['got unexpected type ' node.node_type], line); +end +end + +function X = evalNode(ds, node, line, X) +if strcmp(node.node_type, 'NumConstNode') + X = dseries(str2double(node.value), ds.dates, 'const'); +elseif strcmp(node.node_type, 'VariableNode') + if ~(strcmp(node.type, 'endogenous') ... + || (strcmp(node.type, 'exogenous') && any(strcmp(ds.name, node.name)))) + ols_error(['got unexpected type ' node.name ': ' node.type], line); + end + X = ds.(node.name)(node.lag); +elseif strcmp(node.node_type, 'UnaryOpNode') + Xtmp = evalNode(ds, node.arg, line, X); + % Only works if dseries supports . notation for unary op (true for log/diff) + % Otherwise, use: X = eval([node.op '(Xtmp)']); + X = Xtmp.(node.op); +elseif strcmp(node.node_type, 'BinaryOpNode') + Xtmp1 = evalNode(ds, node.arg1, line, X); + Xtmp2 = evalNode(ds, node.arg2, line, X); + X = X + eval(['Xtmp1 ' node.op ' Xtmp2']); +else + ols_error(['got unexpected node type ' node.node_type], line); +end +end + +function tf = isOlsParam(node) +if strcmp(node.node_type, 'VariableNode') && strcmp(node.type, 'parameter') + tf = true; +else + tf = false; +end +end + +function tf = isOlsParamExpr(node, line) +if strcmp(node.node_type, 'NumConstNode') + tf = true; +elseif strcmp(node.node_type, 'VariableNode') + tf = isOlsParam(node); +elseif strcmp(node.node_type, 'UnaryOpNode') + tf = false; +elseif strcmp(node.node_type, 'BinaryOpNode') + tf = isOlsParamExpr(node.arg1) && isOlsParamExpr(node.arg2); + if tf && ~strcmp(node.op, '-') + ols_error(['got unexpected op ' node.op], line); + end +else + ols_error(['got unexpected type ' node.node_type], line); +end +end