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