Gerbview: evaluate Aperture Macro Parameters using precedence rules for the arithmetic expressions found in AM.
This commit is contained in:
parent
f007601507
commit
e6545ec982
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue