.. _conditionparser-py: ################## conditionparser.py ################## ****************************** class ConditionParser(object): ****************************** Class for parsing conditions in model chain into Reverse Polish Notation form. def __init__(self, expr, validator, name=None): =============================================== Validator is responsible for providing the matrix indices for all the variable, level and operation definitions used in the condition expression:: >>> class Lexicon(object): ... def __init__(self): ... pass ... def variable_ind(self, level, variable): ... if variable == 'AGE': ... return (1, variable) ... elif variable == 'PEAT': ... return (1, variable) ... elif variable == 'SC': ... return (1, variable) ... elif variable == 'TS': ... return (1, variable) ... elif variable == 'MAIN_SP': ... return (1, variable) ... elif variable == 'SP': ... return (2, variable) ... elif variable == 'd': ... return (3, variable) ... elif variable[0] == 'X': ... return (1, variable) ... else: ... return (1,'past_thinning') ... def level_ind(self, level): ... return 2 ... def is_child(self, level_1, level_2): ... return True >>> class XMLObject(object): ... def __init__(self, lexicon): ... self.lexicon = lexicon ... self.errors = [] ... def variable_ind(self, level, variable): ... return self.lexicon.variable_ind(level, variable) ... def level_ind(self, level): ... return self.lexicon.level_ind(level) ... def operation_ind(self, operation): ... if operation == 'thinning': ... return 1 ... else: ... return 2 ... def add_error(self, msg): ... self.errors.append(msg) >>> lexicon = Lexicon() >>> validator = XMLObject(lexicon) >>> from simo.builder.modelchain.conditionparser import ConditionParser >>> cp = ConditionParser(validator, 'comp_unit') Note that normally the Lexicon methods return only indices, but here text is used to illustrate the structure of the parsed output. def parse(self, expr, vars_to_indices=True, no_children=False): =============================================================== Parameters :: expr -- condition expression, string vars_to_incides -- boolean indicating whether the variable names should be transformed to variable indices no_childern -- boolean indicating if children of evaluation level should not be accepted (this is needed for model chain conditions) Parses the SIMO condition expression into a nested list and further into a Reverse Polish notation / Postfix stack:: >>> s0 = 'comp_unit:AGE gt 1.0' >>> c = cp.parse(s0) >>> for i in c: ... print i # doctest: +ELLIPSIS ('data', (1, 'AGE', True)) ('value', 1.0) ('eq', ) >>> s1 = 'comp_unit:AGE gt 1.0 and comp_unit:SC le 4.0' >>> c = cp.parse(s1) >>> for i in c: print i # doctest: +ELLIPSIS ('data', (1, 'AGE', True)) ('value', 1.0) ('eq', ) ('data', (1, 'SC', True)) ('value', 4.0) ('eq', ) ('group', ) >>> s2 = '''(comp_unit:AGE gt 1.0 and comp_unit:SC le 4.0) ... or ... stratum:SP in [1.0, 2.0, 3.0]''' >>> c = cp.parse(s2) >>> for i in c: print i # doctest: +ELLIPSIS ('data', (1, 'AGE', True)) ('value', 1.0) ('eq', ) ('data', (1, 'SC', True)) ('value', 4.0) ('eq', ) ('group', ) ('data', (2, 'SP', True)) ('value', [1.0, 2.0, 3.0]) ('eq', ) ('group', ) >>> s3 = '''((comp_unit:AGE gt 1.0 and comp_unit:SC le 4.0) ... or comp_unit:thinning since_gt 10)''' >>> c = cp.parse(s3) >>> for i in c: print i # doctest: +ELLIPSIS ('data', (1, 'AGE', True)) ('value', 1.0) ('eq', ) ('data', (1, 'SC', True)) ('value', 4.0) ('eq', ) ('group', ) ('op', 1) ('value', 10.0) ('since', ) ('group', ) >>> s4 = '''((comp_unit:AGE gt 1.0 and comp_unit:SC le 4.0) ... or stratum:SP in [1.0, 2.0, 3.0]) ... and ... ((comp_unit:thinning since_gt 10 ... or comp_unit:thinning since_gt comp_unit:past_thinning) ... or stratum not exists)''' >>> c = cp.parse(s4) >>> for i in c: print i # doctest: +ELLIPSIS ('data', (1, 'AGE', True)) ('value', 1.0) ('eq', ) ('data', (1, 'SC', True)) ('value', 4.0) ('eq', ) ('group', ) ('data', (2, 'SP', True)) ('value', [1.0, 2.0, 3.0]) ('eq', ) ('group', ) ('op', 1) ('value', 10.0) ('since', ) ('op', 1) ('data', (1, 'past_thinning', True)) ('since', ) ('group', ) ('level', 2) None ('ex', ) ('group', ) ('group', ) >>> s5 = '''(comp_unit:AGE gt 1.0 and comp_unit:SC le 4.0 ... or stratum:SP in [1.0, 2.0, 3.0]) ... and ... (comp_unit:thinning since_gt 10 ... or comp_unit:thinning since_gt comp_unit:past_thinning ... or stratum not exists)''' >>> c = cp.parse(s5) >>> for i in c: print i # doctest: +ELLIPSIS ('data', (1, 'AGE', True)) ('value', 1.0) ('eq', ) ('data', (1, 'SC', True)) ('value', 4.0) ('eq', ) ('group', ) ('data', (2, 'SP', True)) ('value', [1.0, 2.0, 3.0]) ('eq', ) ('group', ) ('op', 1) ('value', 10.0) ('since', ) ('op', 1) ('data', (1, 'past_thinning', True)) ('since', ) ('group', ) ('level', 2) None ('ex', ) ('group', ) ('group', ) >>> s = 'stratum:SP in [2, 5]' >>> c = cp.parse(s) >>> for i in c: print i # doctest: +ELLIPSIS ('data', (2, 'SP', True)) ('value', [2.0, 5.0]) ('eq', ) >>> s = 'stratum:SP not in [2, 5]' >>> c = cp.parse(s) >>> for i in c: print i # doctest: +ELLIPSIS ('data', (2, 'SP', True)) ('value', [2.0, 5.0]) ('eq', ) >>> s = '''comp_unit:AGE gt 1.0 or comp_unit:SC le 4.0 ... or stratum:SP in [1.0, 2.0, 3.0]''' >>> c = cp.parse(s) >>> for i in c: print i # doctest: +ELLIPSIS ('data', (1, 'AGE', True)) ('value', 1.0) ('eq', ) ('data', (1, 'SC', True)) ('value', 4.0) ('eq', ) ('group', ) ('data', (2, 'SP', True)) ('value', [1.0, 2.0, 3.0]) ('eq', ) ('group', ) >>> s ='comp_unit:AGE exists' >>> c = cp.parse(s) >>> for i in c: print i # doctest: +ELLIPSIS ('data', (1, 'AGE', True)) None ('ex', ) >>> s = 'tree:d gt self:d' >>> c = cp.parse(s) >>> for i in c: print i # doctest: +ELLIPSIS ('data', (3, 'd', True)) ('data', (3, 'd', False)) ('eq', ) >>> cp.validator.errors [] >>> cp.validator.errors = [] >>> s = 'tree:d exists' >>> c = cp.parse(s) >>> for i in c: ... print i # doctest: +ELLIPSIS ('data', (3, 'd', True)) None ('ex', ) >>> s = 'function:random_number_0_1 lt 2.0' >>> c = cp.parse(s) >>> for i in c: ... print i # doctest: +ELLIPSIS ('function', ) ('value', 2.0) ('eq', ) This condition is invalid as the variable level (stratum) is a child level of current evaluation level (comp_unit) :: >>> s0 = 'stratum:SP eq 1' >>> c = cp.parse(s0, no_children=True) >>> for i in c: ... print i # doctest: +ELLIPSIS ('data', (2, 'SP', True)) ('value', 1.0) ('eq', ) >>> cp.validator.errors ["variable 'stratum:SP' cannot be in 'comp_unit' level condition"] >>> cp.validator.errors = [] Examples on how parenthesis affect (or do not affect) the parsed postfix structure :: >>> s = '(comp_unit:SC eq 1 and comp_unit:PEAT eq 2) and (comp_unit:TS gt 1100 and comp_unit:MAIN_SP eq 3)' >>> c = cp.parse(s) >>> for i in c: ... print i # doctest: +ELLIPSIS ('data', (1, 'SC', True)) ('value', 1.0) ('eq', ) ('data', (1, 'PEAT', True)) ('value', 2.0) ('eq', ) ('group', ) ('data', (1, 'TS', True)) ('value', 1100.0) ('eq', ) ('data', (1, 'MAIN_SP', True)) ('value', 3.0) ('eq', ) ('group', ) ('group', ) >>> s = 'comp_unit:SC eq 1 and comp_unit:PEAT eq 2 and comp_unit:TS gt 1100 and comp_unit:MAIN_SP eq 3' >>> c = cp.parse(s) >>> for i in c: ... print i # doctest: +ELLIPSIS ('data', (1, 'SC', True)) ('value', 1.0) ('eq', ) ('data', (1, 'PEAT', True)) ('value', 2.0) ('eq', ) ('group', ) ('data', (1, 'TS', True)) ('value', 1100.0) ('eq', ) ('group', ) ('data', (1, 'MAIN_SP', True)) ('value', 3.0) ('eq', ) ('group', ) >>> s = '((comp_unit:SC eq 1 and comp_unit:PEAT eq 2) and comp_unit:TS gt 1100) and comp_unit:MAIN_SP eq 3' >>> c = cp.parse(s) >>> for i in c: ... print i # doctest: +ELLIPSIS ('data', (1, 'SC', True)) ('value', 1.0) ('eq', ) ('data', (1, 'PEAT', True)) ('value', 2.0) ('eq', ) ('group', ) ('data', (1, 'TS', True)) ('value', 1100.0) ('eq', ) ('group', ) ('data', (1, 'MAIN_SP', True)) ('value', 3.0) ('eq', ) ('group', ) >>> s = '(comp_unit:X1 eq 1) or \ ... (comp_unit:X2 eq 2 and comp_unit:X3 eq 3) or \ ... (comp_unit:X4 eq 4 and comp_unit:X5 eq 5 and comp_unit:X6 eq 6 and comp_unit:X7 eq 7) or \ ... (comp_unit:X8 eq 8 and comp_unit:X9 eq 9 and comp_unit:X10 eq 10) or \ ... (comp_unit:X11 eq 11 and comp_unit:X12 eq 12 and comp_unit:X13 eq 13)' >>> c = cp.parse(s) >>> for i in c: ... print i # doctest: +ELLIPSIS ('data', (1, 'X1', True)) ('value', 1.0) ('eq', ) ('data', (1, 'X2', True)) ('value', 2.0) ('eq', ) ('data', (1, 'X3', True)) ('value', 3.0) ('eq', ) ('group', ) ('group', ) ('data', (1, 'X4', True)) ('value', 4.0) ('eq', ) ('data', (1, 'X5', True)) ('value', 5.0) ('eq', ) ('group', ) ('data', (1, 'X6', True)) ('value', 6.0) ('eq', ) ('group', ) ('data', (1, 'X7', True)) ('value', 7.0) ('eq', ) ('group', ) ('group', ) ('data', (1, 'X8', True)) ('value', 8.0) ('eq', ) ('data', (1, 'X9', True)) ('value', 9.0) ('eq', ) ('group', ) ('data', (1, 'X10', True)) ('value', 10.0) ('eq', ) ('group', ) ('group', ) ('data', (1, 'X11', True)) ('value', 11.0) ('eq', ) ('data', (1, 'X12', True)) ('value', 12.0) ('eq', ) ('group', ) ('data', (1, 'X13', True)) ('value', 13.0) ('eq', ) ('group', ) ('group', ) >>> s = '(comp_unit:X1 eq 1) or \ ... (comp_unit:X2 eq 2 and comp_unit:X3 eq 3) or \ ... (((comp_unit:X4 eq 4 and comp_unit:X5 eq 5) and comp_unit:X6 eq 6) and comp_unit:X7 eq 7) or \ ... ((comp_unit:X8 eq 8 and comp_unit:X9 eq 9) and comp_unit:X10 eq 10) or \ ... ((comp_unit:X11 eq 11 and comp_unit:X12 eq 12) and comp_unit:X13 eq 13)' >>> c = cp.parse(s) >>> for i in c: ... print i # doctest: +ELLIPSIS ('data', (1, 'X1', True)) ('value', 1.0) ('eq', ) ('data', (1, 'X2', True)) ('value', 2.0) ('eq', ) ('data', (1, 'X3', True)) ('value', 3.0) ('eq', ) ('group', ) ('group', ) ('data', (1, 'X4', True)) ('value', 4.0) ('eq', ) ('data', (1, 'X5', True)) ('value', 5.0) ('eq', ) ('group', ) ('data', (1, 'X6', True)) ('value', 6.0) ('eq', ) ('group', ) ('data', (1, 'X7', True)) ('value', 7.0) ('eq', ) ('group', ) ('group', ) ('data', (1, 'X8', True)) ('value', 8.0) ('eq', ) ('data', (1, 'X9', True)) ('value', 9.0) ('eq', ) ('group', ) ('data', (1, 'X10', True)) ('value', 10.0) ('eq', ) ('group', ) ('group', ) ('data', (1, 'X11', True)) ('value', 11.0) ('eq', ) ('data', (1, 'X12', True)) ('value', 12.0) ('eq', ) ('group', ) ('data', (1, 'X13', True)) ('value', 13.0) ('eq', ) ('group', ) ('group', ) >>> s = 'comp_unit:open_area eq 0 and \ ... (comp_unit:USE_RESTRICTION_HARVEST eq 0 or \ ... (comp_unit:USE_RESTRICTION_HARVEST in [5, 6, 7, 8, 9] and \ ... (comp_unit:year gt comp_unit:USE_RESTRICTION_UNTIL_YEAR or \ ... (comp_unit:year eq comp_unit:USE_RESTRICTION_UNTIL_YEAR and \ ... comp_unit:month gt comp_unit:USE_RESTRICTION_UNTIL_MONTH) or \ ... (comp_unit:year eq comp_unit:USE_RESTRICTION_UNTIL_YEAR and \ ... comp_unit:month eq comp_unit:USE_RESTRICTION_UNTIL_MONTH and \ ... comp_unit:day gt comp_unit:USE_RESTRICTION_UNTIL_DAY))))' >>> c = cp.parse(s) >>> for i in c: ... print i # doctest: +ELLIPSIS ('data', (1, 'past_thinning', True)) ('value', 0.0) ('eq', ) ('data', (1, 'past_thinning', True)) ('value', 0.0) ('eq', ) ('data', (1, 'past_thinning', True)) ('value', [5.0, 6.0, 7.0, 8.0, 9.0]) ('eq', ) ('data', (1, 'past_thinning', True)) ('data', (1, 'past_thinning', True)) ('eq', ) ('data', (1, 'past_thinning', True)) ('data', (1, 'past_thinning', True)) ('eq', ) ('data', (1, 'past_thinning', True)) ('data', (1, 'past_thinning', True)) ('eq', ) ('group', ) ('group', ) ('data', (1, 'past_thinning', True)) ('data', (1, 'past_thinning', True)) ('eq', ) ('data', (1, 'past_thinning', True)) ('data', (1, 'past_thinning', True)) ('eq', ) ('group', ) ('data', (1, 'past_thinning', True)) ('data', (1, 'past_thinning', True)) ('eq', ) ('group', ) ('group', ) ('group', ) ('group', ) ('group', ) >>> s = '(comp_unit:X1 eq 1) or \ ... (comp_unit:X2 eq 2 and comp_unit:X3 eq 3) or \ ... ((comp_unit:X4 eq 4 and comp_unit:X5 eq 5) and (comp_unit:X6 eq 6 and comp_unit:X7 eq 7)) or \ ... ((comp_unit:X8 eq 8 and comp_unit:X9 eq 9) and comp_unit:X10 eq 10) or \ ... ((comp_unit:X11 eq 11 and comp_unit:X12 eq 12) and comp_unit:X13 eq 13)' >>> c = cp.parse(s) >>> for i in c: ... print i # doctest: +ELLIPSIS ('data', (1, 'X1', True)) ('value', 1.0) ('eq', ) ('data', (1, 'X2', True)) ('value', 2.0) ('eq', ) ('data', (1, 'X3', True)) ('value', 3.0) ('eq', ) ('group', ) ('group', ) ('data', (1, 'X4', True)) ('value', 4.0) ('eq', ) ('data', (1, 'X5', True)) ('value', 5.0) ('eq', ) ('group', ) ('data', (1, 'X6', True)) ('value', 6.0) ('eq', ) ('data', (1, 'X7', True)) ('value', 7.0) ('eq', ) ('group', ) ('group', ) ('group', ) ('data', (1, 'X8', True)) ('value', 8.0) ('eq', ) ('data', (1, 'X9', True)) ('value', 9.0) ('eq', ) ('group', ) ('data', (1, 'X10', True)) ('value', 10.0) ('eq', ) ('group', ) ('group', ) ('data', (1, 'X11', True)) ('value', 11.0) ('eq', ) ('data', (1, 'X12', True)) ('value', 12.0) ('eq', ) ('group', ) ('data', (1, 'X13', True)) ('value', 13.0) ('eq', ) ('group', ) ('group', ) Failing conditions:: >>> f = 'comp_unit;AGE gt 1.0' >>> c = cp.parse(f) >>> cp.validator.errors ['Error parsing expression "comp_unit;AGE gt 1.0", Expected ":" at char 9, got ";AGE"'] >>> cp.validator.errors = [] >>> f = 'comp_unit:AGE gt 1,0' >>> c = cp.parse(f) >>> cp.validator.errors ['Error parsing expression "comp_unit:AGE gt 1,0", Expected end of text at char 18, got ",0"'] >>> cp.validator.errors = [] >>> f = 'comp_unit:AGE > 1.0' >>> c = cp.parse(f) >>> cp.validator.errors ['Error parsing expression "comp_unit:AGE > 1.0", Expected Re:(\'gt|ge|eq|ue|le|lt\') at char 14, got ">"'] >>> cp.validator.errors = [] >>> f = 'comp_unit:AGE gt 1.0 and comp_unit:SC <= 4.0' >>> c = cp.parse(f) >>> cp.validator.errors ['Error parsing expression "comp_unit:AGE gt 1.0 and comp_unit:SC <= 4.0", Expected Re:(\'gt|ge|eq|ue|le|lt\') at char 38, got "<="'] >>> cp.validator.errors = [] >>> f = '''((comp_unit:AGE gt 1.0 and comp_unit:SC le 4.0) ... or stratum:SP in [1.0, 2.0, 3.0]) ... and ... ((comp_unit:thinning since_gt 10 ... or comp_unit:thinning since_gt comp_unit:past_thinning) ... or stratum not exist)''' >>> c = cp.parse(f) >>> cp.validator.errors ['Error parsing expression "((comp_unit:AGE gt 1.0 and comp_unit:SC le 4.0) or stratum:SP in [1.0, 2.0, 3.0]) and ((comp_unit:thinning since_gt 10 or comp_unit:thinning since_gt comp_unit:past_thinning) or stratum not exist)", Expected ":" at char 186, got "not"'] >>> cp.validator.errors = [] >>> f = 'comp_unit:AGE gt 1.0 and SC lt 4.0' >>> c = cp.parse(f) >>> cp.validator.errors ['Error parsing expression "comp_unit:AGE gt 1.0 and SC lt 4.0", Expected ":" at char 28, got "lt"'] def __construct_condition(self, cond): ====================================== Responsible for the actual processing of the parsed nested list into a RPN stack. def __process_triplet(self, cond): ================================== Processess the atomic condition of [variable, operator, value] or [variable, operator, variable] or [level, exists operator] or [operation, operator, value] or [operation, operator, variable]. Also recursively calls the _construct_condition to divide the [condition, boolean operator, condition] cases. def __set_variable(self, dtype, vardef): ======================================== Retrieves the indices needed for 'data', 'op' and 'level' type operands.