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
dcode.cpp
draw_gerber_screen.cpp
evaluate.cpp
events_called_functions.cpp
excellon_read_drill_file.cpp
export_to_pcbnew.cpp

View File

@ -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
ops.push_back( AM_PARAM_EVAL( curr_value ) );
break;
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;
ops.push_back( AM_PARAM_EVAL( curr_value ) );
default:
wxLogDebug( wxT( "AM_PARAM::GetValue() : unexpected operator\n" ) );
break;
}
break;
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;
}
/**

View File

@ -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> AM_PARAM_EVAL_STACK;
/**
* Class AM_PARAM
* 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;
}