libeval_compiler: initial version

This commit is contained in:
Tomasz Wlostowski 2020-06-04 13:04:03 +02:00
parent bff209b07c
commit b82ec2161f
9 changed files with 3128 additions and 1 deletions

View File

@ -53,6 +53,7 @@ add_subdirectory( eeschema )
add_subdirectory( libs )
add_subdirectory( utils/kicad2step )
add_subdirectory( libeval_compiler )
# Utility/debugging/profiling programs
add_subdirectory( common_tools )

View File

@ -0,0 +1,107 @@
#
# This program source code file is part of KiCad, a free EDA CAD application.
#
# Copyright (C) 2017 CERN
# @author Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
#
# 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
find_package(Boost COMPONENTS unit_test_framework REQUIRED)
find_package( wxWidgets 3.0.0 COMPONENTS gl aui adv html core net base xml stc REQUIRED )
find_program(LEMON lemon)
if( LEMON )
macro( generate_lemon_grammar TGT_NAME GRAMMAR_LEMON GRAMMAR_C )
add_custom_target( ${TGT_NAME}
DEPENDS ${GRAMMAR_LEMON}
COMMAND ${LEMON} -q ${GRAMMAR_LEMON}
COMMENT "Running Lemon on ${GRAMMAR_LEMON} -> ${GRAMMAR_C}"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
endmacro()
generate_lemon_grammar( libeval_grammar2 grammar.lemon grammar.c )
endif()
add_definitions(-DBOOST_TEST_DYN_LINK -DPCBNEW)
if( BUILD_GITHUB_PLUGIN )
set( GITHUB_PLUGIN_LIBRARIES github_plugin )
endif()
add_dependencies( pnsrouter pcbcommon pcad2kicadpcb libeval_grammar2 ${GITHUB_PLUGIN_LIBRARIES} )
add_executable( libeval_compiler_test
libeval_compiler_test.cpp
libeval_compiler.cpp
../qa_utils/mocks.cpp
../../common/base_units.cpp
../../3d-viewer/3d_viewer/3d_viewer_settings.cpp
)
include_directories( BEFORE ${INC_BEFORE} )
include_directories(
${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/3d-viewer
${CMAKE_SOURCE_DIR}/common
${CMAKE_SOURCE_DIR}/pcbnew
${CMAKE_SOURCE_DIR}/pcbnew/router
${CMAKE_SOURCE_DIR}/pcbnew/tools
${CMAKE_SOURCE_DIR}/pcbnew/dialogs
${CMAKE_SOURCE_DIR}/polygon
${CMAKE_SOURCE_DIR}/common/geometry
${CMAKE_SOURCE_DIR}/qa/common
${CMAKE_SOURCE_DIR}/qa/qa_utils
${Boost_INCLUDE_DIR}
${INC_AFTER}
)
target_link_libraries( libeval_compiler_test
pnsrouter
common
pcbcommon
bitmaps
pnsrouter
common
pcbcommon
bitmaps
pnsrouter
common
pcbcommon
bitmaps
pnsrouter
common
pcbcommon
bitmaps
gal
pcad2kicadpcb
altium2kicadpcb
common
pcbcommon
${GITHUB_PLUGIN_LIBRARIES}
common
pcbcommon
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}
${wxWidgets_LIBRARIES}
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
#define G_IDENTIFIER 1
#define G_ASSIGN 2
#define G_SEMCOL 3
#define G_BOOL_OR 4
#define G_BOOL_AND 5
#define G_BOOL_XOR 6
#define G_LESS_THAN 7
#define G_GREATER_THAN 8
#define G_LESS_EQUAL_THAN 9
#define G_GREATER_EQUAL_THAN 10
#define G_EQUAL 11
#define G_NOT_EQUAL 12
#define G_UNIT 13
#define G_BOOL_NOT 14
#define G_PLUS 15
#define G_MINUS 16
#define G_DIVIDE 17
#define G_MULT 18
#define G_STRUCT_REF 19
#define G_ENDS 20
#define G_VALUE 21
#define G_STRING 22
#define G_PARENL 23
#define G_PARENR 24

View File

@ -0,0 +1,77 @@
/*
This file is part of libeval, a simple math expression evaluator
Copyright (C) 2017 Michael Geselbracht, mgeselbracht3@gmail.com
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 3 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, see <https://www.gnu.org/licenses/>.
*/
%token_type { LIBEVAL::TREE_NODE }
%extra_argument { LIBEVAL::COMPILER* pEval }
%nonassoc G_IDENTIFIER G_ASSIGN G_SEMCOL.
%nonassoc G_BOOL_OR G_BOOL_AND G_BOOL_XOR.
%nonassoc G_LESS_THAN G_GREATER_THAN G_LESS_EQUAL_THAN G_GREATER_EQUAL_THAN.
%nonassoc G_EQUAL G_NOT_EQUAL.
%right G_UNIT.
%left G_BOOL_NOT.
%left G_PLUS G_MINUS.
%left G_DIVIDE G_MULT.
%nonassoc G_STRUCT_REF.
%include {
#include <assert.h>
#include "libeval_compiler.h"
}
%syntax_error {
pEval->parseError("Syntax error");
}
%parse_accept {
pEval->parseOk();
}
main ::= in.
/* Allow multiple statements in input string: x=1; y=2 */
in ::= stmt.
in ::= in stmt.
/* A statement can be empty, an expr or an expr followed by ';' */
stmt ::= G_ENDS.
stmt ::= expr(A) G_ENDS. { pEval->setRoot(A); }
//stmt ::= expr G_SEMCOL. { pEval->setRoot(NULL) }
expr(A) ::= G_VALUE(B). { A.op = TR_NUMBER; A.value = B.value; A.leaf[0] = A.leaf[1] = NULL; A.valid = true; }
expr(A) ::= G_VALUE(B) G_UNIT(C). { A.op = TR_NUMBER; A.value = B.value; A.leaf[0] = newNode( TR_UNIT, C.value.type, ""); A.leaf[1] = NULL; A.valid = true; }
expr(A) ::= G_STRING(B). { A.op = TR_STRING; A.value = B.value; A.leaf[0] = A.leaf[1] = NULL; A.valid = true; }
expr(A) ::= G_IDENTIFIER(B). { A.op = TR_IDENTIFIER; A.value = B.value; A.leaf[0] = A.leaf[1] = NULL; A.valid = true; }
expr(A) ::= expr(B) G_LESS_THAN expr(C). { A.op = TR_OP_LESS; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_GREATER_THAN expr(C). { A.op = TR_OP_GREATER; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_LESS_EQUAL_THAN expr(C). { A.op = TR_OP_LESS_EQUAL; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_GREATER_EQUAL_THAN expr(C). { A.op = TR_OP_GREATER_EQUAL; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_NOT_EQUAL expr(C). { A.op = TR_OP_NOT_EQUAL; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_BOOL_AND expr(C). { A.op = TR_OP_BOOL_AND; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_BOOL_OR expr(C). { A.op = TR_OP_BOOL_OR; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_PLUS expr(C). { A.op = TR_OP_ADD; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_MINUS expr(C). { A.op = TR_OP_SUB; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_MULT expr(C). { A.op = TR_OP_MUL; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_DIVIDE expr(C). { A.op = TR_OP_DIV; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_EQUAL expr(C). { A.op = TR_OP_EQUAL; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= expr(B) G_STRUCT_REF expr(C). { A.op = TR_STRUCT_REF; A.leaf[0] = copyNode(B); A.leaf[1] = copyNode(C); A.valid=B.valid && C.valid; }
expr(A) ::= G_PARENL expr(B) G_PARENR. { A.op = B.op; A.value = B.value; A.valid=B.valid; A.leaf[0] = B.leaf[0]; A.leaf[1] = B.leaf[1]; }
expr(A) ::= G_IDENTIFIER(func_name) G_PARENL expr(B) G_PARENR. { A.op = TR_OP_FUNC_CALL; A.leaf[0] = copyNode(func_name); A.leaf[1] = copyNode(B); A.valid=1; }

View File

@ -0,0 +1,735 @@
/*
This file is part of libeval, a simple math expression evaluator
Copyright (C) 2017 Michael Geselbracht, mgeselbracht3@gmail.com
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 3 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, see <https://www.gnu.org/licenses/>.
*/
#include <set>
#include <vector>
#ifdef DEBUG
#include <stdarg.h>
#endif
#include "libeval_compiler.h"
/* The (generated) lemon parser is written in C.
* In order to keep its symbol from the global namespace include the parser code with
* a C++ namespace.
*/
namespace LIBEVAL
{
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wsign-compare"
#endif
#include "grammar.c"
#include "grammar.h"
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
static void libeval_dbg( const char* fmt, ... )
{
#ifdef DEBUG
va_list ap;
va_start( ap, fmt );
fprintf( stderr, "libeval: " );
vfprintf( stderr, fmt, ap );
va_end( ap );
#endif
}
static const std::string formatOpName( int op )
{
static const struct
{
int op;
std::string mnemonic;
} simpleOps[] = { { TR_OP_MUL, "MUL" }, { TR_OP_DIV, "DIV" }, { TR_OP_ADD, "ADD" },
{ TR_OP_SUB, "SUB" }, { TR_OP_LESS, "LESS" }, { TR_OP_GREATER, "GREATER" },
{ TR_OP_LESS_EQUAL, "LESS_EQUAL" }, { TR_OP_GREATER_EQUAL, "GREATER_EQUAL" },
{ TR_OP_EQUAL, "EQUAL" }, { TR_OP_NOT_EQUAL, "NEQUAL" }, { TR_OP_BOOL_AND, "AND" },
{ TR_OP_BOOL_OR, "OR" }, { TR_OP_BOOL_NOT, "NOT" }, { -1, "" } };
for( int i = 0; simpleOps[i].op >= 0; i++ )
{
if( simpleOps[i].op == op )
{
return simpleOps[i].mnemonic;
}
}
return "???";
}
std::string UCODE::UOP::Format() const
{
char str[1024];
switch( m_op )
{
case TR_UOP_PUSH_VAR:
sprintf( str, "PUSH VAR [%p]", m_arg );
break;
case TR_UOP_PUSH_VALUE:
{
auto val = reinterpret_cast<VALUE*>( m_arg );
if( val->GetType() == VT_NUMERIC )
sprintf( str, "PUSH NUM [%.10f]", val->AsDouble() );
else
sprintf( str, "PUSH STR [%s]", val->AsString().c_str() );
break;
}
default:
sprintf( str, "%s", formatOpName( m_op ).c_str() );
break;
}
return str;
}
std::string UCODE::Dump() const
{
std::string rv;
for( auto op : m_ucode )
{
rv += op->Format();
rv += "\n";
}
return rv;
};
std::string TOKENIZER::GetChars( std::function<bool( int )> cond ) const
{
std::string rv;
size_t p = m_pos;
// printf("p %d len %d\n", p, str.length() );
while( p < m_str.length() && cond( m_str[p] ) )
{
rv.append( 1, m_str[p] );
p++;
}
return rv;
}
bool TOKENIZER::MatchAhead( std::string match, std::function<bool( int )> stopCond ) const
{
int remaining = m_str.length() - m_pos;
if( remaining < (int) match.length() )
return false;
if( m_str.substr( m_pos, match.length() ) == match )
{
return ( remaining == (int) match.length() || stopCond( m_str[m_pos + match.length()] ) );
}
return false;
}
COMPILER::COMPILER()
{
m_localeDecimalSeparator = '.';
m_parseError = false;
m_parseFinished = false;
m_unitResolver.reset( new UNIT_RESOLVER );
m_parser = LIBEVAL::ParseAlloc( malloc );
}
COMPILER::~COMPILER()
{
LIBEVAL::ParseFree( m_parser, free );
// Allow explicit call to destructor
m_parser = nullptr;
Clear();
}
void COMPILER::Clear()
{
//free( current.token );
m_tokenizer.Clear();
m_parseError = true;
}
void COMPILER::parseError( const char* s )
{
m_parseError = true;
}
void COMPILER::parseOk()
{
m_parseFinished = true;
}
bool COMPILER::Compile( const std::string& aString, UCODE* aCode )
{
// Feed parser token after token until end of input.
newString( aString );
m_tree = nullptr;
m_parseError = false;
m_parseFinished = false;
T_TOKEN tok;
libeval_dbg( "str: '%s' empty: %d\n", aString.c_str(), !!aString.empty() );
if( aString.empty() )
{
m_parseFinished = true;
return generateUCode( aCode );
}
do
{
tok = getToken();
libeval_dbg( "parse: tok %d\n", tok.token );
Parse( m_parser, tok.token, tok.value, this );
//printf('error')
if( m_parseError )
{
//printf( "PARSE ERR\n" );
m_parseErrorToken = "";
m_parseErrorPos = m_tokenizer.GetPos();
return false;
}
if( m_parseFinished || tok.token == G_ENDS )
{
// Reset parser by passing zero as token ID, value is ignored.
Parse( m_parser, 0, tok.value, this );
break;
}
} while( tok.token );
return generateUCode( aCode );
}
void COMPILER::newString( const std::string& aString )
{
Clear();
m_lexerState = LS_DEFAULT;
m_tokenizer.Restart( aString );
m_parseFinished = false;
}
COMPILER::T_TOKEN COMPILER::getToken()
{
T_TOKEN rv;
bool done = false;
do
{
switch( m_lexerState )
{
case LS_DEFAULT:
done = lexDefault( rv );
break;
case LS_STRING:
done = lexString( rv );
break;
}
//printf( "-> lstate %d done %d\n", m_lexerState, !!done );
} while( !done );
return rv;
}
bool COMPILER::lexString( COMPILER::T_TOKEN& aToken )
{
auto str = m_tokenizer.GetChars( []( int c ) -> bool { return c != '"'; } );
//printf("STR LIT '%s'\n", (const char *)str.c_str() );
aToken.token = G_STRING;
strcpy( aToken.value.value.str, str.c_str() );
m_tokenizer.NextChar( str.length() + 1 );
m_lexerState = LS_DEFAULT;
return true;
}
int COMPILER::resolveUnits()
{
int unitId = 0;
for( auto unitName : m_unitResolver->GetSupportedUnits() )
{
if( m_tokenizer.MatchAhead( unitName, []( int c ) -> bool { return !isalnum( c ); } ) )
{
libeval_dbg( "Match unit '%s'\n", unitName.c_str() );
m_tokenizer.NextChar( unitName.length() );
return unitId;
}
unitId++;
}
return -1;
}
bool COMPILER::lexDefault( COMPILER::T_TOKEN& aToken )
{
T_TOKEN retval;
std::string current;
size_t idx;
int convertFrom;
retval.token = G_ENDS;
//printf( "tokdone %d\n", !!m_tokenizer.Done() );
if( m_tokenizer.Done() )
{
aToken = retval;
return true;
}
auto isDecimalSeparator = [&]( char ch ) -> bool {
return ( ch == m_localeDecimalSeparator || ch == '.' || ch == ',' );
};
// Lambda: get value as string, store into clToken.token and update current index.
auto extractNumber = [&]() {
bool haveSeparator = false;
idx = 0;
auto ch = m_tokenizer.GetChar();
do
{
if( isDecimalSeparator( ch ) && haveSeparator )
break;
current.append( 1, ch );
if( isDecimalSeparator( ch ) )
haveSeparator = true;
m_tokenizer.NextChar();
ch = m_tokenizer.GetChar();
} while( isdigit( ch ) || isDecimalSeparator( ch ) );
// Ensure that the systems decimal separator is used
for( int i = current.length(); i; i-- )
if( isDecimalSeparator( current[i - 1] ) )
current[i - 1] = m_localeDecimalSeparator;
//printf("-> NUM: '%s'\n", (const char *) current.c_str() );
};
int ch;
// Start processing of first/next token: Remove whitespace
for( ;; )
{
ch = m_tokenizer.GetChar();
if( ch == ' ' )
m_tokenizer.NextChar();
else
break;
}
libeval_dbg( "LEX ch '%c' pos %d\n", ch, m_tokenizer.GetPos() );
if( ch == 0 )
{
/* End of input */
}
else if( isdigit( ch ) )
{
// VALUE
extractNumber();
retval.token = G_VALUE;
strcpy( retval.value.value.str, current.c_str() );
}
else if( ( convertFrom = resolveUnits() ) >= 0 )
{
//printf("unit\n");
// UNIT
// Units are appended to a VALUE.
// Determine factor to default unit if unit for value is given.
// Example: Default is mm, unit is inch: factor is 25.4
// The factor is assigned to the terminal UNIT. The actual
// conversion is done within a parser action.
retval.token = G_UNIT;
retval.value.value.type = convertFrom;
}
else if( ch == '\"' ) // string literal
{
//printf( "MATCH STRING LITERAL\n" );
m_lexerState = LS_STRING;
m_tokenizer.NextChar();
return false;
}
else if( isalpha( ch ) )
{
//printf("ALPHA\n");
current = m_tokenizer.GetChars( []( int c ) -> bool { return isalnum( c ); } );
//printf("Len: %d\n", current.length() );
//printf("id '%s'\n", (const char *) current.c_str() );
fflush( stdout );
retval.token = G_IDENTIFIER;
strcpy( retval.value.value.str, current.c_str() );
m_tokenizer.NextChar( current.length() );
}
else if( m_tokenizer.MatchAhead( "==", []( int c ) -> bool { return c != '='; } ) )
{
retval.token = G_EQUAL;
m_tokenizer.NextChar( 2 );
//printf( "nc pos %d\n", m_tokenizer.GetPos() );
}
else if( m_tokenizer.MatchAhead( "<=", []( int c ) -> bool { return c != '='; } ) )
{
retval.token = G_LESS_EQUAL_THAN;
m_tokenizer.NextChar( 2 );
}
else if( m_tokenizer.MatchAhead( ">=", []( int c ) -> bool { return c != '='; } ) )
{
retval.token = G_GREATER_EQUAL_THAN;
m_tokenizer.NextChar( 2 );
}
else if( m_tokenizer.MatchAhead( "&&", []( int c ) -> bool { return c != '&'; } ) )
{
retval.token = G_BOOL_AND;
m_tokenizer.NextChar( 2 );
}
else if( m_tokenizer.MatchAhead( "||", []( int c ) -> bool { return c != '|'; } ) )
{
retval.token = G_BOOL_OR;
m_tokenizer.NextChar( 2 );
}
else
{
//printf( "WTF: '%c'\n", ch );
// Single char tokens
switch( ch )
{
case '+':
retval.token = G_PLUS;
break;
case '!':
retval.token = G_BOOL_NOT;
break;
case '-':
retval.token = G_MINUS;
break;
case '*':
retval.token = G_MULT;
break;
case '/':
retval.token = G_DIVIDE;
break;
case '<':
retval.token = G_LESS_THAN;
break;
case '>':
retval.token = G_GREATER_THAN;
break;
case '(':
retval.token = G_PARENL;
break;
case ')':
retval.token = G_PARENR;
break;
case ';':
retval.token = G_SEMCOL;
break;
case '.':
retval.token = G_STRUCT_REF;
break;
default:
m_parseError = true;
break; /* invalid character */
}
m_tokenizer.NextChar();
}
aToken = retval;
return true;
}
const std::string formatNode( TREE_NODE* tok )
{
char str[1024];
// printf("fmt tok %p v %p ", tok, tok->value.v );
fflush( stdout );
sprintf( str, "%s", (const char*) tok->value.str );
return str;
}
void dumpNode( std::string& buf, TREE_NODE* tok, int depth = 0 )
{
char str[1024];
sprintf( str, "\n[%p] ", tok ); //[tok %p] ", tok);
buf += str;
for( int i = 0; i < 2 * depth; i++ )
buf += " ";
if( tok->op & TR_OP_BINARY_MASK )
{
sprintf( str, "%s", (const char*) formatOpName( tok->op ).c_str() );
buf += str;
dumpNode( buf, tok->leaf[0], depth + 1 );
dumpNode( buf, tok->leaf[1], depth + 1 );
}
switch( tok->op )
{
case TR_NUMBER:
sprintf( str, "NUMERIC: " );
buf += str;
sprintf( str, "%s", formatNode( tok ).c_str() );
buf += str;
if( tok->leaf[0] )
dumpNode( buf, tok->leaf[0], depth + 1 );
break;
case TR_STRING:
sprintf( str, "STRING: " );
buf += str;
sprintf( str, "%s", formatNode( tok ).c_str() );
buf += str;
break;
case TR_IDENTIFIER:
sprintf( str, "ID: " );
buf += str;
sprintf( str, "%s", formatNode( tok ).c_str() );
buf += str;
break;
case TR_STRUCT_REF:
sprintf( str, "SREF: " );
buf += str;
dumpNode( buf, tok->leaf[0], depth + 1 );
dumpNode( buf, tok->leaf[1], depth + 1 );
break;
case TR_UNIT:
sprintf( str, "UNIT: %d ", tok->value.type );
buf += str;
break;
}
}
void COMPILER::setRoot( TREE_NODE root )
{
m_tree = copyNode( root );
}
bool COMPILER::generateUCode( UCODE* aCode )
{
std::vector<TREE_NODE*> stack;
std::set<TREE_NODE*> visitedNodes;
auto visited = [&]( TREE_NODE* node ) -> bool {
return visitedNodes.find( node ) != visitedNodes.end();
};
UCODE code;
assert( m_tree );
stack.push_back( m_tree );
//printf("compile: tree %p\n", m_tree);
while( !stack.empty() )
{
auto node = stack.back();
bool isTerminalNode = true;
// printf( "process node %p [op %d] [stack %d]\n", node, node->op, stack.size() );
// process terminal nodes first
switch( node->op )
{
case TR_STRUCT_REF:
{
assert( node->leaf[0]->op == TR_IDENTIFIER );
assert( node->leaf[1]->op == TR_IDENTIFIER );
auto vref = aCode->createVarRef( node->leaf[0]->value.str, node->leaf[1]->value.str );
aCode->AddOp( TR_UOP_PUSH_VAR, vref );
break;
}
case TR_NUMBER:
{
auto son = node->leaf[0];
double value = atof( node->value.str ); // fixme: locale
if( son && son->op == TR_UNIT )
{
//printf( "HandleUnit: %s unit %d\n", node->value.str, son->value.type );
value = m_unitResolver->Convert( node->value.str, son->value.type );
visitedNodes.insert( son );
}
aCode->AddOp( TR_UOP_PUSH_VALUE, value );
break;
}
case TR_STRING:
{
aCode->AddOp( TR_UOP_PUSH_VALUE, node->value.str );
break;
}
default:
isTerminalNode = false;
break;
}
if( isTerminalNode )
{
visitedNodes.insert( node );
stack.pop_back();
continue;
}
if( node->leaf[0] && !visited( node->leaf[0] ) )
{
stack.push_back( node->leaf[0] );
}
else if( node->leaf[1] && !visited( node->leaf[1] ) )
{
stack.push_back( node->leaf[1] );
}
else
{
aCode->AddOp( node->op );
visitedNodes.insert( node );
stack.pop_back();
}
}
return true;
}
void UCODE::UOP::Exec( CONTEXT* ctx, UCODE* ucode )
{
switch( m_op )
{
case TR_UOP_PUSH_VAR:
{
auto value = ctx->AllocValue();
value->Set( reinterpret_cast<VAR_REF*>( m_arg )->GetValue( ucode ) );
ctx->Push( value );
break;
}
case TR_UOP_PUSH_VALUE:
ctx->Push( reinterpret_cast<VALUE*>( m_arg ) );
return;
default:
break;
}
if( m_op & TR_OP_BINARY_MASK )
{
auto arg2 = ctx->Pop();
auto arg1 = ctx->Pop();
double result;
switch( m_op )
{
case TR_OP_ADD:
result = arg1->AsDouble() + arg2->AsDouble();
break;
case TR_OP_SUB:
result = arg1->AsDouble() - arg2->AsDouble();
break;
case TR_OP_MUL:
result = arg1->AsDouble() * arg2->AsDouble();
break;
case TR_OP_DIV:
result = arg1->AsDouble() / arg2->AsDouble();
break;
case TR_OP_LESS_EQUAL:
result = arg1->AsDouble() <= arg2->AsDouble() ? 1 : 0;
break;
case TR_OP_GREATER_EQUAL:
result = arg1->AsDouble() >= arg2->AsDouble() ? 1 : 0;
break;
case TR_OP_LESS:
result = arg1->AsDouble() < arg2->AsDouble() ? 1 : 0;
break;
case TR_OP_GREATER:
result = arg1->AsDouble() > arg2->AsDouble() ? 1 : 0;
break;
case TR_OP_EQUAL:
result = arg1->EqualTo( arg2 ) ? 1 : 0;
break;
case TR_OP_NOT_EQUAL:
result = arg1->EqualTo( arg2 ) ? 0 : 1;
break;
case TR_OP_BOOL_AND:
result = ( ( arg1->AsDouble() != 0.0 ? true : false )
&& ( arg2->AsDouble() != 0.0 ? true : false ) ) ?
1 :
0;
break;
case TR_OP_BOOL_OR:
result = ( ( arg1->AsDouble() != 0.0 ? true : false )
|| ( arg2->AsDouble() != 0.0 ? true : false ) ) ?
1 :
0;
break;
default:
result = 0.0;
break;
}
auto rp = ctx->AllocValue();
rp->Set( result );
ctx->Push( rp );
return;
}
else if( m_op & TR_OP_UNARY_MASK )
{
// fixme : not operator
}
}
VALUE* UCODE::Run()
{
CONTEXT ctx;
for( const auto op : m_ucode )
op->Exec( &ctx, this );
assert( ctx.SP() == 1 );
return ctx.Pop();
}
} // namespace LIBEVAL

View File

@ -0,0 +1,471 @@
/*
This file is part of libeval, a simple math expression evaluator
Copyright (C) 2007 Michael Geselbracht, mgeselbracht3@gmail.com
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 3 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, see <https://www.gnu.org/licenses/>.
*/
#ifndef __LIBEVAL_COMPILER_H
#define __LIBEVAL_COMPILER_H
#include <cstddef>
#include <functional>
#include <map>
#include <string>
#include <base_units.h>
#define TR_OP_BINARY_MASK 0x200
#define TR_OP_UNARY_MASK 0x100
#define TR_OP_MUL 0x201
#define TR_OP_DIV 0x202
#define TR_OP_ADD 0x203
#define TR_OP_SUB 0x204
#define TR_OP_LESS 0x25
#define TR_OP_GREATER 0x206
#define TR_OP_LESS_EQUAL 0x207
#define TR_OP_GREATER_EQUAL 0x208
#define TR_OP_EQUAL 0x209
#define TR_OP_NOT_EQUAL 0x20a
#define TR_OP_BOOL_AND 0x20b
#define TR_OP_BOOL_OR 0x20c
#define TR_OP_BOOL_NOT 0x100
#define TR_OP_FUNC_CALL 24
#define TR_UOP_PUSH_VAR 1
#define TR_UOP_PUSH_VALUE 2
// This namespace is used for the lemon parser
namespace LIBEVAL
{
struct ERROR_STATUS
{
bool pendingError;
enum STAGE {
CST_PARSE = 0,
CST_CODEGEN,
CST_RUNTIME
};
std::string message;
std::string failingObject;
int failingPosition;
};
enum VAR_TYPE_T
{
VT_STRING = 1,
VT_NUMERIC,
VT_UNDEFINED
};
enum TOKEN_TYPE_T
{
TR_NUMBER = 1,
TR_IDENTIFIER = 2,
TR_ASSIGN = 3,
TR_STRUCT_REF = 4,
TR_STRING = 5,
TR_UNIT = 6
};
#define LIBEVAL_MAX_LITERAL_LENGTH 1024
struct TREE_NODE
{
struct value_s
{
char str[LIBEVAL_MAX_LITERAL_LENGTH];
int type;
} value;
int op;
TREE_NODE* leaf[2];
bool valid;
};
static inline TREE_NODE* copyNode( TREE_NODE& t )
{
auto t2 = new TREE_NODE();
t2->valid = t.valid;
strcpy(t2->value.str, t.value.str);
t2->op = t.op;
t2->value.type = t.value.type;
t2->leaf[0] = t.leaf[0];
t2->leaf[1] = t.leaf[1];
return t2;
}
static inline TREE_NODE* newNode( int op, int type, std::string value )
{
auto t2 = new TREE_NODE();
t2->valid = true;
strcpy(t2->value.str, value.c_str());
t2->op = op;
t2->value.type = type;
t2->leaf[0] = nullptr;
t2->leaf[1] = nullptr;
return t2;
}
class UNIT_RESOLVER {
public:
UNIT_RESOLVER()
{
}
virtual ~UNIT_RESOLVER()
{
}
virtual const std::vector<std::string>& GetSupportedUnits() const
{
static const std::vector<std::string> nullUnits;
return nullUnits;
}
virtual double Convert( const std::string aString, int unitType ) const
{
return 0.0;
};
};
class VALUE {
public:
VALUE():
m_type(VT_UNDEFINED),
m_valueDbl( 0 )
{};
VALUE( const std::string& aStr ) :
m_type( VT_STRING ),
m_valueDbl( 0 ),
m_valueStr( aStr )
{};
VALUE( const double aVal ) :
m_type( VT_NUMERIC ),
m_valueDbl( aVal )
{};
double AsDouble() const
{
return m_valueDbl;
}
const std::string& AsString() const
{
return m_valueStr;
}
bool operator==( const VALUE& b ) const
{
if( m_type != b.m_type )
return false;
if( m_type == VT_NUMERIC && m_valueDbl != b.m_valueDbl )
return false;
if( m_type == VT_STRING && m_valueStr != b.m_valueStr )
return false;
return true;
}
VAR_TYPE_T GetType() const { return m_type; };
void Set( double aValue )
{
m_type = VT_NUMERIC;
m_valueDbl = aValue;
}
void Set( std::string aValue )
{
m_type = VT_STRING;
m_valueStr = aValue;
}
void Set( const VALUE &val )
{
m_type = val.m_type;
m_valueDbl = val.m_valueDbl;
if(m_type == VT_STRING)
m_valueStr = val.m_valueStr;
}
bool EqualTo( const VALUE* v2 ) const
{
assert ( m_type == v2->m_type );
if(m_type == VT_STRING)
return m_valueStr == v2->m_valueStr;
else
return m_valueDbl == v2->m_valueDbl;
}
private:
VAR_TYPE_T m_type;
double m_valueDbl;
std::string m_valueStr;
};
class UCODE
{
public:
class CONTEXT
{
public:
const int c_memSize = 128;
CONTEXT()
{
m_sp = 0;
for (int i = 0; i < c_memSize; i++ )
m_memory.push_back( VALUE() );
}
VALUE* AllocValue()
{
assert( m_memPos < c_memSize );
auto rv = &m_memory[ m_memPos++ ];
return rv;
}
void Push( VALUE* v )
{
m_stack[m_sp++] = v;
}
VALUE* Pop()
{
m_sp--;
return m_stack[m_sp];
}
int SP() const
{
return m_sp;
}
private:
std::vector<VALUE> m_memory;
VALUE* m_stack[128];
int m_sp = 0;
int m_memPos = 0;
};
class UOP
{
public:
UOP( int op, void* arg ) : m_op( op ), m_arg( arg )
{};
void Exec( CONTEXT* ctx, UCODE *ucode );
std::string Format() const;
private:
int m_op;
void *m_arg;
};
class VAR_REF
{
public:
virtual VAR_TYPE_T GetType( const UCODE* aUcode ) const = 0;
virtual VALUE GetValue( const UCODE* aUcode ) const = 0;
};
void AddOp( int op, double value )
{
auto uop = new UOP( op, new VALUE( value ) );
m_ucode.push_back( uop );
}
void AddOp( int op, std::string value )
{
auto uop = new UOP( op, new VALUE( value ) );
m_ucode.push_back( uop );
}
void AddOp( int op, VAR_REF* aRef = nullptr )
{
auto uop = new UOP( op, aRef );
m_ucode.push_back( uop );
}
VALUE* Run();
std::string Dump() const;
virtual VAR_REF* createVarRef( const std::string& var, const std::string& field )
{
return NULL;
};
private:
std::vector<UOP*> m_ucode;
};
class TOKENIZER
{
public:
void Restart( const std::string& aStr )
{
m_str = aStr;
m_pos = 0;
}
void Clear()
{
m_str = "";
m_pos = 0;
}
int GetChar() const
{
if( m_pos >= m_str.length() )
return 0;
return m_str[m_pos];
}
bool Done() const
{
return m_pos >= m_str.length();
}
void NextChar( int aAdvance = 1 )
{
m_pos += aAdvance;
}
size_t GetPos() const
{
return m_pos;
}
std::string GetChars( std::function<bool( int )> cond ) const;
bool MatchAhead( std::string match, std::function<bool( int )> stopCond ) const;
private:
std::string m_str;
size_t m_pos;
};
class COMPILER
{
public:
COMPILER();
virtual ~COMPILER();
/* clear() should be invoked by the client if a new input string is to be processed. It
* will reset the parser. User defined variables are retained.
*/
void Clear();
/* Used by the lemon parser */
void parseError( const char* s );
void parseOk();
/* Check if previous invokation of process() was successful */
inline bool IsValid() const
{
return !m_parseError;
}
const std::string GetParseErrorToken() const
{
return m_parseErrorToken;
}
const std::string GetParseErrorMessage() const
{
return m_parseErrorMessage;
}
int GetParseErrorPosition() const
{
return m_parseErrorPos;
}
void setRoot( LIBEVAL::TREE_NODE root );
bool Compile( const std::string& aString, UCODE* aCode );
std::string DumpTree();
protected:
enum LEXER_STATE
{
LS_DEFAULT = 0,
LS_STRING = 1,
};
LEXER_STATE m_lexerState;
bool generateUCode( UCODE* aCode );
/* Token type used by the tokenizer */
struct T_TOKEN
{
int token;
TREE_NODE value;
};
/* Begin processing of a new input string */
void newString( const std::string& aString );
/* Tokenizer: Next token/value taken from input string. */
T_TOKEN getToken();
bool lexDefault( T_TOKEN& aToken );
bool lexString( T_TOKEN& aToken );
/* Used by processing loop */
void parse( int token, TREE_NODE value );
void* m_parser; // the current lemon parser state machine
int resolveUnits();
/* Token state for input string. */
TOKENIZER m_tokenizer;
char m_localeDecimalSeparator;
/* Parse progress. Set by parser actions. */
bool m_parseError;
bool m_parseFinished;
std::string m_parseErrorMessage;
std::string m_parseErrorToken;
int m_parseErrorPos;
std::unique_ptr<UNIT_RESOLVER> m_unitResolver;
TREE_NODE* m_tree;
};
} // namespace LIBEVAL
#endif /* LIBEVAL_COMPILER_H_ */

View File

@ -0,0 +1,324 @@
#include <wx/wx.h>
#include <cstdio>
#include "class_board.h"
#include "class_track.h"
#include "libeval_compiler.h"
#include <io_mgr.h>
#include <kicad_plugin.h>
#include <unordered_set>
#include <profile.h>
class PCB_EXPR_VAR_REF;
class PCB_EXPR_UCODE : public LIBEVAL::UCODE
{
public:
virtual VAR_REF *createVarRef( const std::string &var, const std::string &field ) override;
void SetItems( BOARD_ITEM *a, BOARD_ITEM* b )
{
m_items[0] = a;
m_items[1] = b;
}
BOARD_ITEM *GetItem( int index ) const
{
return m_items[index];
}
private:
BOARD_ITEM *m_items[2];
};
class PCB_EXPR_VAR_REF : public LIBEVAL::UCODE::VAR_REF
{
public:
PCB_EXPR_VAR_REF ( int aItemIndex )
: m_itemIndex(aItemIndex)
{
//printf("*** createVarRef %p %d\n", this, aItemIndex );
}
void SetType( LIBEVAL::VAR_TYPE_T type )
{
m_type = type;
}
void AddAllowedClass ( TYPE_ID type_hash, PROPERTY_BASE *prop )
{
m_matchingTypes[type_hash] = prop;
}
virtual LIBEVAL::VAR_TYPE_T GetType( const LIBEVAL::UCODE* aUcode ) const override
{
return m_type;
}
virtual LIBEVAL::VALUE GetValue( const LIBEVAL::UCODE* aUcode ) const override
{
auto ucode = static_cast<const PCB_EXPR_UCODE*> (aUcode);
auto item = ucode->GetItem( m_itemIndex );
auto it = m_matchingTypes.find( TYPE_HASH( *item ) );
if( it == m_matchingTypes.end() )
{
printf("Null field!\n");
return LIBEVAL::VALUE(0.0);
}
else
{
if( m_type == LIBEVAL::VT_NUMERIC )
return LIBEVAL::VALUE( (double) item->Get<int>( it->second ) );
else
{
wxString str = item->Get<wxString>( it->second );
//printf("item %p GetStr '%s'\n", item, (const char*) str.c_str());
return LIBEVAL::VALUE( (const char*) str.c_str() );
}
}
}
private:
std::unordered_map<TYPE_ID, PROPERTY_BASE*> m_matchingTypes;
int m_itemIndex;
LIBEVAL::VAR_TYPE_T m_type;
};
LIBEVAL::UCODE::VAR_REF *PCB_EXPR_UCODE::createVarRef( const std::string &var, const std::string &field )
{
PCB_EXPR_VAR_REF *rv;
PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
auto classes = propMgr.GetAllClasses();
auto vref = new PCB_EXPR_VAR_REF( var == "A" ? 0 : 1 );
for ( auto cls : classes )
{
if( propMgr.IsOfType( cls.type, TYPE_HASH( BOARD_ITEM ) ) )
{
PROPERTY_BASE* prop = propMgr.GetProperty( cls.type, field );
if( prop )
{
//printf("Field '%s' class %s ptr %p\n", field.c_str(), (const char *) cls.name.c_str(), prop );
vref->AddAllowedClass( cls.type, prop );
if ( prop->TypeHash() == TYPE_HASH(int) )
vref->SetType( LIBEVAL::VT_NUMERIC );
else if ( prop->TypeHash() == TYPE_HASH(wxString) )
vref->SetType( LIBEVAL::VT_STRING );
else {
printf("Unknown property type\n");
}
}
}
}
return vref;
}
BOARD* loadBoard( const std::string& filename )
{
PLUGIN::RELEASER pi( new PCB_IO );
BOARD* brd = nullptr;
try
{
brd = pi->Load( wxString( filename.c_str() ), NULL, NULL );
}
catch( const IO_ERROR& ioe )
{
wxString msg = wxString::Format( _( "Error loading board.\n%s" ),
ioe.Problem() );
printf( "%s\n", (const char*) msg.mb_str() );
return nullptr;
}
return brd;
}
class PCB_UNIT_RESOLVER : public LIBEVAL::UNIT_RESOLVER
{
public:
virtual ~PCB_UNIT_RESOLVER()
{
}
virtual const std::vector<std::string>& GetSupportedUnits() const override
{
static const std::vector<std::string> pcbUnits = {"mil", "mm", "in"};
return pcbUnits;
}
virtual double Convert( const std::string aString, int unitId ) const override
{
double v = atof(aString.c_str());
switch(unitId)
{
case 0 :
return Mils2iu( v );
case 1:
return Millimeter2iu( v );
case 2:
return Mils2iu( v * 1000.0 );
default:
return v;
}
};
};
class PCB_EXPR_COMPILER : public LIBEVAL::COMPILER
{
public:
PCB_EXPR_COMPILER()
{
m_unitResolver.reset( new PCB_UNIT_RESOLVER );
}
};
bool testEvalExpr( const std::string expr, LIBEVAL::VALUE expectedResult, bool expectError = false, BOARD_ITEM* itemA = nullptr, BOARD_ITEM* itemB = nullptr )
{
PCB_EXPR_COMPILER compiler;
PCB_EXPR_UCODE ucode;
bool ok = true;
ucode.SetItems( itemA, itemB );
bool error = !compiler.Compile( expr, &ucode );
if( error )
{
if ( expectError )
{
printf("result: OK (expected parse error)\n");
ok = true;
return ok;
} else {
printf("result: FAIL (unexpected parse error)\n");
ok = false;
}
}
LIBEVAL::VALUE result;
if( ok )
{
result = *ucode.Run();
ok = (result == expectedResult);
}
if( expectedResult.GetType() == LIBEVAL::VT_NUMERIC )
printf("result: %s (got %.10f expected: %.10f)\n", ok ? "OK" : "FAIL", result.AsDouble(), expectedResult.AsDouble() );
else
printf("result: %s (got '%s' expected: '%s')\n", ok ? "OK" : "FAIL", result.AsString().c_str(), expectedResult.AsString().c_str() );
if (!ok )
{
printf("Offending code dump: \n%s\n", ucode.Dump().c_str() );
}
return ok;
}
bool EvaluatePCBExpression( const std::string& aExpr, int& aResult )
{
PCB_EXPR_COMPILER compiler;
PCB_EXPR_UCODE ucode;
if( !compiler.Compile( aExpr, &ucode ) )
return false;
auto result = ucode.Run();
return true;
}
class PCB_EXPR_EVALUATOR
{
public:
PCB_EXPR_EVALUATOR();
~PCB_EXPR_EVALUATOR();
bool Evaluate( const wxString& aExpr );
int Result() const { return m_result; }
wxString GetErrorString();
private:
bool m_error;
int m_result;
PCB_EXPR_COMPILER m_compiler;
PCB_EXPR_UCODE m_ucode;
}
int main( int argc, char *argv[] )
{
PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
propMgr.Rebuild();
using VAL = LIBEVAL::VALUE;
/* testEvalExpr( "10mm + 20 mm", VAL(30e6) );
testEvalExpr( "3*(7+8)", VAL(3*(7+8)) );
testEvalExpr( "3*7+8", VAL(3*7+8) );
testEvalExpr( "(3*7)+8", VAL(3*7+8) );
testEvalExpr( "10mm + 20)", VAL(0), true );
*/
BOARD brd;
NETINFO_LIST& netInfo = brd.GetNetInfo();
NETCLASSPTR netclass1( new NETCLASS("HV") );
NETCLASSPTR netclass2( new NETCLASS("otherClass" ) );
printf("netcl1 classname '%s'\n", (const char *) netclass1->GetName().c_str() );
auto net1info = new NETINFO_ITEM( &brd, "net1", 1);
auto net2info = new NETINFO_ITEM( &brd, "net2", 2);
net1info->SetClass( netclass1 );
net2info->SetClass( netclass2 );
printf("netX classname '%s'\n", net1info->GetClassName().c_str() );
printf("net1 class %p %p\n", net1info->GetNetClass(), netclass1.get() );
TRACK trackA(&brd);
TRACK trackB(&brd);
printf("net1 classname '%s'\n", (const char*) net1info->GetClassName().c_str() );
trackA.SetNet( net1info );
trackB.SetNet( net2info );
trackA.SetWidth( Mils2iu( 10 ));
trackB.SetWidth( Mils2iu( 20 ));
printf("TrkA %p netclass '%s'\n", &trackA, (const char*) trackA.GetNetClassName().c_str() );
printf("TrkB %p netclass '%s'\n", &trackB, (const char*) trackB.GetNetClassName().c_str() );
testEvalExpr( "A.Width > B.Width", VAL(0.0), false, &trackA, &trackB );
testEvalExpr( "A.Width + B.Width", VAL(Mils2iu(10) + Mils2iu(20)), false, &trackA, &trackB );
testEvalExpr( "A.Netclass", VAL( (const char*) trackA.GetNetClassName().c_str() ), false, &trackA, &trackB );
testEvalExpr( "A.Netclass == \"HV\" && B.netclass == \"otherClass\"", VAL( 1.0 ), false, &trackA, &trackB );
testEvalExpr( "A.Netclass + 1.0", VAL( 1.0 ), false, &trackA, &trackB );
return 0;
}

View File

@ -121,6 +121,7 @@ program;
PGM_BASE& Pgm()
{
printf("Pgm @ %p\n", &program );
return program;
}
@ -129,7 +130,7 @@ PGM_BASE& Pgm()
// is run from a python script, mot from a Kicad application
PGM_BASE* PgmOrNull()
{
return &program;
return nullptr; //&program;
}