diff --git a/gerbview/CMakeLists.txt b/gerbview/CMakeLists.txt index c63272a1f9..060afeec6e 100644 --- a/gerbview/CMakeLists.txt +++ b/gerbview/CMakeLists.txt @@ -43,6 +43,7 @@ set( GERBVIEW_SRCS controle.cpp dcode.cpp draw_gerber_screen.cpp + evaluate.cpp events_called_functions.cpp excellon_read_drill_file.cpp export_to_pcbnew.cpp diff --git a/gerbview/class_am_param.cpp b/gerbview/class_am_param.cpp index 51a23ce1a3..74a181842b 100644 --- a/gerbview/class_am_param.cpp +++ b/gerbview/class_am_param.cpp @@ -32,6 +32,7 @@ extern int ReadInt( char*& text, bool aSkipSeparator = true ); extern double ReadDouble( char*& text, bool aSkipSeparator = true ); +extern double Evaluate( AM_PARAM_EVAL_STACK& aExp ); /* Class AM_PARAM * holds a parameter value for an "aperture macro" as defined within @@ -53,24 +54,34 @@ AM_PARAM::AM_PARAM( ) */ bool AM_PARAM::IsImmediate() const { - bool isimmediate = true; + bool is_immediate = true; for( unsigned ii = 0; ii < m_paramStack.size(); ii++ ) { if( m_paramStack[ii].IsDefered() ) { // a defered value is found in operand list, // so the parameter is not immediate - isimmediate = false; + is_immediate = false; break; } } - return isimmediate; + return is_immediate; } double AM_PARAM::GetValue( const D_CODE* aDcode ) const { - double paramvalue = 0.0; + // In macros, actual values are sometimes given by an expression like: + // 0-$2/2-$4 + // Because arithmetic predence is used, the parameters (values (double) and operators) + // are stored in a stack, with all numeric values converted to the actual values + // when they are defered parameters + // Each item is stored in a AM_PARAM_EVAL (a value or an operator) + // + // Then the stack with all values resolved is parsed and numeric values + // calculated according to the precedence of operators double curr_value = 0.0; - parm_item_type state = POPVALUE; + parm_item_type op_code; + + AM_PARAM_EVAL_STACK ops; for( unsigned ii = 0; ii < m_paramStack.size(); ii++ ) { @@ -81,12 +92,15 @@ double AM_PARAM::GetValue( const D_CODE* aDcode ) const case ADD: case SUB: case MUL: - case DIV: // just an operator for next parameter value: store it - state = item.GetType(); + case DIV: // just an operator for next parameter value + case OPEN_PAR: + case CLOSE_PAR: // Priority modifiers: store in stack + op_code = item.GetType(); + ops.push_back( AM_PARAM_EVAL( op_code ) ); break; case PUSHPARM: - // get the parameter from the aDcode + // a defered value: get the actual parameter from the aDcode if( aDcode ) // should be always true here { if( item.GetIndex() <= aDcode->GetParamCount() ) @@ -103,54 +117,23 @@ double AM_PARAM::GetValue( const D_CODE* aDcode ) const { wxLogDebug( wxT( "AM_PARAM::GetValue(): NULL param aDcode\n" ) ); } - // Fall through - case PUSHVALUE: // a value is on the stack: - if( item.GetType() == PUSHVALUE ) - curr_value = item.GetValue(); - switch( state ) - { - case POPVALUE: - paramvalue = curr_value; - break; - - case ADD: - paramvalue += curr_value; - break; - - case SUB: - paramvalue -= curr_value; - break; - - case MUL: - paramvalue *= curr_value; - break; - - case DIV: - paramvalue /= curr_value; - break; - - case OPEN_PAR: - // Currently: do nothing because operator precedence is not yet considered - break; - - case CLOSE_PAR: - // Currently: do nothing because operator precedence is not yet considered - break; - - default: - wxLogDebug( wxT( "AM_PARAM::GetValue() : unexpected operator\n" ) ); - break; - } + ops.push_back( AM_PARAM_EVAL( curr_value ) ); break; + case PUSHVALUE: // a value is on the stack: + curr_value = item.GetValue(); + ops.push_back( AM_PARAM_EVAL( curr_value ) ); + default: - wxLogDebug( wxT( "AM_PARAM::GetValue(): unexpected type\n" ) ); + wxLogDebug( "AM_PARAM::GetValue(): unexpected type\n" ); break; } } - return paramvalue; + double result = Evaluate( ops ); + + return result; } /** diff --git a/gerbview/class_am_param.h b/gerbview/class_am_param.h index 5f949e638d..fbcbe200d3 100644 --- a/gerbview/class_am_param.h +++ b/gerbview/class_am_param.h @@ -114,9 +114,11 @@ Precedence was recently actually defined in gerber RS274X (Previously, there was no actual info about this precedence) This is the usual arithmetic precendence between + - x / ( ), the only ones used in Gerber -However, because it is recent, currently, actual value is calculated step to step: +Before 2015 apr 10, actual value was calculated step to step: no precedence, and '(' ')' are ignored. +Since 2015 apr 10 precedence is in use. + Parameter definition is described by a very primitive assembler. This "program "should describe how to calculate the parameter. The assembler consist of 10 instruction intended for a stackbased machine. @@ -148,6 +150,65 @@ enum parm_item_type NOP, PUSHVALUE, PUSHPARM, ADD, SUB, MUL, DIV, OPEN_PAR, CLOSE_PAR, POPVALUE }; +/** + * This helper class hold a value or an arithmetic operator to calculate + * the final value of a aperture macro parameter, using usual + * arithmetic operator precedence + * Only operators ADD, SUB, MUL, DIV, OPEN_PAR, CLOSE_PAR have meaning when calculating + * a value + */ +class AM_PARAM_EVAL +{ +public: + AM_PARAM_EVAL( parm_item_type aType ) + : m_type( aType), m_dvalue( 0.0 ) + {} + + AM_PARAM_EVAL( double aValue ) + : m_type( parm_item_type::NOP ), m_dvalue( aValue ) + {} + + parm_item_type GetType() const + { + return m_type; + } + + bool IsOperator() const { return m_type != NOP; } + double GetValue() const { return m_dvalue; } + parm_item_type GetOperator() const { return m_type; } + int GetPriority() const { return GetPriority( GetOperator() ); } + + static int GetPriority( parm_item_type aType ) + { + switch( aType ) + { + case ADD: + case SUB: + return 1; + + case MUL: + case DIV: + return 2; + + case OPEN_PAR: + case CLOSE_PAR: + return 3; + + default: + break; + } + + return 0; + } + +private: + parm_item_type m_type; // the type of item + double m_dvalue; // the value, for a numerical value + // used only when m_type == NOP +}; + +typedef std::vector AM_PARAM_EVAL_STACK; + /** * Class AM_PARAM * holds an operand for an AM_PARAM as defined within diff --git a/gerbview/evaluate.cpp b/gerbview/evaluate.cpp new file mode 100644 index 0000000000..22a45eff33 --- /dev/null +++ b/gerbview/evaluate.cpp @@ -0,0 +1,240 @@ +/** + * @file evaluate.cpp + */ + +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 1992-2017 Jean-Pierre Charras + * Copyright (C) 1992-2017 KiCad Developers, see change_log.txt for contributors. + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* How to evaluate an arithmetic expression like those used in Aperture Macro Definition in Gerber? + * + * See http://stackoverflow.com/questions/28256/equation-expression-parser-with-precedence + * + * The shunting yard algorithm is the right tool for this. + * Wikipedia is really confusing about this, but basically the algorithm works like this: + * + * Say, you want to evaluate 1 + 2 * 3 + 4. Intuitively, you "know" you have to do the 2 * 3 first, + * but how do you get this result? + * The key is to realize that when you're scanning the string from left to right, you will evaluate + * an operator when the operator that follows it has a lower (or equal to) precedence. + * + * In the context of the example, here's what you want to do: + * + * Look at: 1 + 2, don't do anything. + * Now look at 1 + 2 * 3, still don't do anything. + * Now look at 1 + 2 * 3 + 4, now you know that 2 * 3 has to to be evaluated because + * the next operator has lower precedence. + * + * How do you implement this? + * + * You want to have two stacks, one for numbers, and another for operators. + * You push numbers onto the stack all the time. + * You compare each new operator with the one at the top of the stack, + * if the one on top of the stack has higher priority, you pop it off the operator stack, + * pop the operands off the number stack, apply the operator and push the result onto the number stack. + * + * Now you repeat the comparison with the top of stack operator. + * + * Coming back to the example, it works like this: + * + * N = [ ] Ops = [ ] + * + * Read 1. N = [1], Ops = [ ] + * Read +. N = [1], Ops = [+] + * Read 2. N = [1 2], Ops = [+] + * Read *. N = [1 2], Ops = [+ *] + * Read 3. N = [1 2 3], Ops = [+ *] + * Read +. N = [1 2 3], Ops = [+ *] + * Pop 3, 2 and execute 2*3, and push result onto N. N = [1 6], Ops = [+] + * is left associative, so you want to pop 1, 6 off as well and execute the +. N = [7], Ops = []. + * Finally push the [+] onto the operator stack. N = [7], Ops = [+]. + * Read 4. N = [7 4]. Ops = [+]. + * + * You're run out off input, so you want to empty the stacks now. + * Upon which you will get the result 11. + */ + +#include + +/** + * Evaluate an basic arithmetic expression (infix notation) with precedence + * The expression is a sequence of numbers (double) and arith operators: + * operators are + - x / ( and ) + * the expression is stored in a std::vector + * each item is a AM_PARAM_EVAL (each item is an operator or a double) + * @param aExp = the arithmetic expression to evaluate + * @return the value + */ + + /* + The instructions ( subset of parm_item_type) +---------------- +NOP : The no operation. the AM_PARAM_EVAL item stores a value. +ADD +SUB +MUL +DIV +OPEN_PAR : Opening parenthesis: modify the precedence of operators inside ( and ) +CLOSE_PAR : Closing parenthesis: modify the precedence of operators by closing the local block. +POPVALUE : used to initialize a sequence +*/ + +double Evaluate( AM_PARAM_EVAL_STACK& aExp ) +{ + class OP_CODE // A small class to store a operator and its priority + { + public: + parm_item_type m_Optype; + int m_Priority; + + OP_CODE( AM_PARAM_EVAL& aAmPrmEval ) + : m_Optype( aAmPrmEval.GetOperator() ), + m_Priority( aAmPrmEval.GetPriority() ) + {} + + OP_CODE( parm_item_type aOptype ) + : m_Optype( aOptype ), m_Priority( 0 ) + {} + }; + + double result = 0.0; + + std::vector values; // the current list of values + std::vector optype; // the list of arith operators + + double curr_value = 0.0; + int extra_priority = 0; + + for( unsigned ii = 0; ii < aExp.size(); ii++ ) + { + AM_PARAM_EVAL& prm = aExp[ii]; + + if( prm.IsOperator() ) + { + if( prm.GetOperator() == OPEN_PAR ) + { + extra_priority += AM_PARAM_EVAL::GetPriority( OPEN_PAR ); + } + else if( prm.GetOperator() == CLOSE_PAR ) + { + extra_priority -= AM_PARAM_EVAL::GetPriority( CLOSE_PAR ); + } + else + { + optype.push_back( OP_CODE( prm ) ); + optype.back().m_Priority += extra_priority; + } + } + else // we have a value: + { + values.push_back( prm.GetValue() ); + + if( optype.size() < 2 ) + continue; + + OP_CODE& previous_optype = optype[optype.size() - 2]; + + if( optype.back().m_Priority > previous_optype.m_Priority ) + { + double op1 = 0.0; + + double op2 = values.back(); + values.pop_back(); + + if( values.size() ) + { + op1 = values.back(); + values.pop_back(); + } + + switch( optype.back().m_Optype ) + { + case ADD: + values.push_back( op1+op2 ); + break; + + case SUB: + values.push_back( op1-op2 ); + break; + + case MUL: + values.push_back( op1*op2 ); + break; + + case DIV: + values.push_back( op1/op2 ); + break; + + default: + break; + } + + optype.pop_back(); + } + } + } + + // Now all operators have the same priority, or those having the higher priority + // are before others, calculate the final result by combining initial values and/or + // replaced values. + if( values.size() > optype.size() ) + // If there are n values, the number of operator is n-1 or n if the first + // item of the expression to evaluate is + or - (like -$1/2) + // If the number of operator is n-1 the first value is just copied to result + optype.insert( optype.begin(), OP_CODE( POPVALUE ) ); + + wxASSERT( values.size() == optype.size() ); + + for( unsigned idx = 0; idx < values.size(); idx++ ) + { + curr_value = values[idx]; + + switch( optype[idx].m_Optype ) + { + case POPVALUE: + result = curr_value; + break; + + case ADD: + result += curr_value; + break; + + case SUB: + result -= curr_value; + break; + + case MUL: + result *= curr_value; + break; + + case DIV: + result /= curr_value; + break; + + default: + break; + } + } + + return result; +} \ No newline at end of file