Gerbview: evaluate Aperture Macro Parameters using precedence rules for the arithmetic expressions found in AM.

This commit is contained in:
jean-pierre charras 2017-04-10 17:29:25 +02:00
parent f007601507
commit e6545ec982
4 changed files with 334 additions and 49 deletions

View File

@ -43,6 +43,7 @@ set( GERBVIEW_SRCS
controle.cpp controle.cpp
dcode.cpp dcode.cpp
draw_gerber_screen.cpp draw_gerber_screen.cpp
evaluate.cpp
events_called_functions.cpp events_called_functions.cpp
excellon_read_drill_file.cpp excellon_read_drill_file.cpp
export_to_pcbnew.cpp export_to_pcbnew.cpp

View File

@ -32,6 +32,7 @@
extern int ReadInt( char*& text, bool aSkipSeparator = true ); extern int ReadInt( char*& text, bool aSkipSeparator = true );
extern double ReadDouble( char*& text, bool aSkipSeparator = true ); extern double ReadDouble( char*& text, bool aSkipSeparator = true );
extern double Evaluate( AM_PARAM_EVAL_STACK& aExp );
/* Class AM_PARAM /* Class AM_PARAM
* holds a parameter value for an "aperture macro" as defined within * 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 AM_PARAM::IsImmediate() const
{ {
bool isimmediate = true; bool is_immediate = true;
for( unsigned ii = 0; ii < m_paramStack.size(); ii++ ) for( unsigned ii = 0; ii < m_paramStack.size(); ii++ )
{ {
if( m_paramStack[ii].IsDefered() ) if( m_paramStack[ii].IsDefered() )
{ // a defered value is found in operand list, { // a defered value is found in operand list,
// so the parameter is not immediate // so the parameter is not immediate
isimmediate = false; is_immediate = false;
break; break;
} }
} }
return isimmediate; return is_immediate;
} }
double AM_PARAM::GetValue( const D_CODE* aDcode ) const 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; 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++ ) 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 ADD:
case SUB: case SUB:
case MUL: case MUL:
case DIV: // just an operator for next parameter value: store it case DIV: // just an operator for next parameter value
state = item.GetType(); case OPEN_PAR:
case CLOSE_PAR: // Priority modifiers: store in stack
op_code = item.GetType();
ops.push_back( AM_PARAM_EVAL( op_code ) );
break; break;
case PUSHPARM: 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( aDcode ) // should be always true here
{ {
if( item.GetIndex() <= aDcode->GetParamCount() ) 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" ) ); 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 ) ops.push_back( AM_PARAM_EVAL( curr_value ) );
{
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;
}
break; break;
case PUSHVALUE: // a value is on the stack:
curr_value = item.GetValue();
ops.push_back( AM_PARAM_EVAL( curr_value ) );
default: default:
wxLogDebug( wxT( "AM_PARAM::GetValue(): unexpected type\n" ) ); wxLogDebug( "AM_PARAM::GetValue(): unexpected type\n" );
break; break;
} }
} }
return paramvalue; double result = Evaluate( ops );
return result;
} }
/** /**

View File

@ -114,9 +114,11 @@ Precedence was recently actually defined in gerber RS274X
(Previously, there was no actual info about this precedence) (Previously, there was no actual info about this precedence)
This is the usual arithmetic precendence between + - x / ( ), the only ones used in Gerber 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. no precedence, and '(' ')' are ignored.
Since 2015 apr 10 precedence is in use.
Parameter definition is described by a very primitive assembler. Parameter definition is described by a very primitive assembler.
This "program "should describe how to calculate the parameter. This "program "should describe how to calculate the parameter.
The assembler consist of 10 instruction intended for a stackbased machine. 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 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> AM_PARAM_EVAL_STACK;
/** /**
* Class AM_PARAM * Class AM_PARAM
* holds an operand for an AM_PARAM as defined within * holds an operand for an AM_PARAM as defined within

240
gerbview/evaluate.cpp Normal file
View File

@ -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 <jp.charras at wanadoo.fr>
* 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 <class_am_param.h>
/**
* 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<double> values; // the current list of values
std::vector<OP_CODE> 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;
}