2024-06-23 19:40:39 +02:00
/*
ProcNode - Implementation of render node processing
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
Copyright ( C )
2024 , Hermann Vosseler < Ichthyostega @ web . de >
2024-06-23 19:40:39 +02:00
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
* * Lumiera * * 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 . See the file COPYING for further details .
2024-06-23 19:40:39 +02:00
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2024-06-23 19:40:39 +02:00
/** @file proc-node.cpp
* * Translation unit to hold the actual implementation of node processing operations .
* *
* * @ todo WIP - WIP - WIP 6 / 2024 not clear yet what goes here and what goes into turnout - system . cpp
*/
2024-11-02 23:54:41 +01:00
# include "steam/engine/proc-id.hpp"
2024-06-23 19:40:39 +02:00
# include "steam/engine/proc-node.hpp"
2024-11-04 23:56:16 +01:00
# include "lib/iter-explorer.hpp"
2024-11-03 21:03:34 +01:00
# include "lib/format-string.hpp"
2024-11-04 23:56:16 +01:00
# include "lib/format-util.hpp"
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
# include "lib/regex.hpp"
2024-11-03 21:03:34 +01:00
# include "lib/util.hpp"
2025-01-11 17:05:53 +01:00
# include <boost/functional/hash.hpp> /////////////////////////////////////////////////////TICKET #1391 is boost-hash the proper tool for this task?
2024-11-03 21:03:34 +01:00
# include <unordered_set>
2024-11-04 23:56:16 +01:00
# include <set>
2024-06-23 19:40:39 +02:00
2024-11-03 22:55:06 +01:00
2024-06-23 19:40:39 +02:00
namespace steam {
namespace engine {
2024-11-04 23:56:16 +01:00
using lib : : explore ;
2024-11-03 23:58:25 +01:00
using util : : _Fmt ;
using util : : isnil ;
2024-11-03 21:03:34 +01:00
using util : : unConst ;
2024-11-03 23:58:25 +01:00
using util : : contains ;
using boost : : hash_combine ;
2024-06-23 19:40:39 +02:00
Library: need support for specification parsing
Unfortunately, there are some common syntactic structures, which can not easily be dissected by regular expressions alone, since they entail nested subexpressions. While it is possible to get beyond those fundamental limitations with some trickery, doing so remains precisely that, ''trickery.''
After fighting some inner conflicts, since ''I do know how to write a parser'' —
in the end I have brought myself to just do it.
And indeed, as you'd might expect, I have looked into existing library solutions,
and I would not like to have any one of them as part of the project.
* I do not want a ''parser engine'' or ''parser generator''
* I want the directness of recursive-descent, but combined with Regular Expressions as terminal
* I want to see the structure of the used grammar at the definition site of the custom parser function
* I want deep integration of ''model bindings'' into the parse process, i.e. binding-λ
* I do not want to write model-dissecting or pattern-matching code after the parse
* I do not want to expose ''Monads'' as an interface, since they tend to spread unhealthy structure to surrounding code
* I do not want to leak technicalities of the parse mechanics into the using code
* I do not want to impose hard to remember specific conventions onto the user
Thus I've set the following aims:
* The usage should require only a single header include (ideally header-only)
* The entrance point should be a small number of DSL-starter functions
* The parser shall be implemented by recursive-descent, using the parser-combinator technique
* But I want that wrapped into a DSL, to be able to control what is (not) provided or exposed.
* I want a stateful, applicative logic, since parsing, by its very nature, is stateful!
* I want complete compile-time typing, visible to the optimiser, without a virtual »Parser« interface
And last but not least, ''I do not want to create a ticket, since I do not know if those goals can be achieved...''
2025-01-17 18:40:44 +01:00
namespace { // Details: parsing, registration and symbol table for node spec data...
2024-11-03 21:03:34 +01:00
std : : unordered_set < ProcID > procRegistry ;
2024-11-04 01:04:01 +01:00
std : : unordered_set < string > symbRegistry ;
2024-06-23 19:40:39 +02:00
2024-11-04 23:56:16 +01:00
/** deduplicate and re-link to the entry in the symbol table */
2024-11-04 01:04:01 +01:00
void inline
dedupSymbol ( StrView & symbol )
{
auto res = symbRegistry . emplace ( symbol ) ;
symbol = * res . first ;
}
2024-06-23 19:40:39 +02:00
} // (END) Details...
Port : : ~ Port ( ) { } ///< @remark VTables for the Port-Turnout hierarchy emitted from \ref proc-node.cpp
2024-11-03 21:03:34 +01:00
/**
* @ remark this is the only public access point to ProcID entries ,
* which are automatically deduplicated and managed in a common registry
* and retained until end of the Lumiera process ( never deleted ) .
*/
2024-11-02 23:54:41 +01:00
ProcID &
2024-11-03 22:55:06 +01:00
ProcID : : describe ( StrView nodeSymb , StrView portSpec )
2024-11-02 23:54:41 +01:00
{
2024-11-03 23:58:25 +01:00
REQUIRE ( not isnil ( nodeSymb ) ) ;
REQUIRE ( not isnil ( portSpec ) ) ;
REQUIRE ( not contains ( nodeSymb , ' ' ) ) ;
auto p = portSpec . find ( ' ( ' ) ;
if ( p = = string : : npos )
throw err : : Invalid { _Fmt { " Spec for processing operation must contain at least one argument list. "
" Node:%s Spec:%s " }
% nodeSymb % portSpec
} ;
auto res = procRegistry . insert ( ProcID { nodeSymb , portSpec . substr ( 0 , p ) , portSpec . substr ( p ) } ) ;
2024-11-04 01:04:01 +01:00
ProcID & entry { unConst ( * res . first ) } ;
if ( res . second )
{ // new record placed into the registry
2025-01-11 22:17:07 +01:00
dedupSymbol ( entry . nodeName_ ) ;
2024-11-04 01:04:01 +01:00
dedupSymbol ( entry . argLists_ ) ;
if ( not isnil ( entry . portQual_ ) )
dedupSymbol ( entry . portQual_ ) ;
}
return entry ;
2024-11-02 23:54:41 +01:00
}
2024-06-23 19:40:39 +02:00
/** @internal */
2024-11-03 22:55:06 +01:00
ProcID : : ProcID ( StrView nodeSymb , StrView portQual , StrView argLists )
2025-01-11 22:17:07 +01:00
: nodeName_ { nodeSymb }
2024-11-03 22:55:06 +01:00
, portQual_ { portQual }
, argLists_ { argLists }
{ }
2024-06-23 19:40:39 +02:00
2024-11-04 23:56:16 +01:00
/**
* generate registry hash value based on the distinct data in ProcID .
* This function is intended to be picked up by ADL , and should be usable
* both with ` std : : hash ` and ` < boost / functional / hash . hpp > ` .
2024-11-03 21:03:34 +01:00
*/
HashVal
hash_value ( ProcID const & procID )
{
2025-01-11 22:17:07 +01:00
HashVal hash = boost : : hash_value ( procID . nodeName_ ) ; ///////////////////////////////////////////////////TICKET #1391 : which technology to use for processing-ID hashes -> cache keys?
2024-11-03 23:58:25 +01:00
if ( not isnil ( procID . portQual_ ) )
2025-01-11 17:05:53 +01:00
hash_combine ( hash , procID . portQual_ ) ; ////////////////////////////////////////////////////////TICKET #1391 : should use lib/hash-combine.hpp (stable, but not portable!)
2024-11-03 23:58:25 +01:00
hash_combine ( hash , procID . argLists_ ) ;
return hash ;
2024-11-03 21:03:34 +01:00
}
2024-11-04 23:56:16 +01:00
string
ProcID : : genProcName ( )
{
std : : ostringstream buffer ;
2025-01-11 22:17:07 +01:00
buffer < < genNodeSymbol ( )
< < genQualifier ( ) ;
2024-11-04 23:56:16 +01:00
return buffer . str ( ) ;
}
2024-11-03 23:58:25 +01:00
string
ProcID : : genProcSpec ( )
{
2024-11-04 01:04:01 +01:00
std : : ostringstream buffer ;
2025-01-11 22:17:07 +01:00
buffer < < nodeName_
< < genQualifier ( )
< < argLists_ ;
2024-11-04 01:04:01 +01:00
return buffer . str ( ) ;
2024-11-03 23:58:25 +01:00
}
2024-11-04 23:56:16 +01:00
string
ProcID : : genNodeName ( )
{
2025-01-11 22:17:07 +01:00
return string { nodeName_ } ;
}
string
ProcID : : genNodeSymbol ( )
{
auto p = nodeName_ . find ( ' : ' ) ;
return p = = string : : npos ? string { nodeName_ }
: string { nodeName_ . substr ( p + 1 ) } ;
}
string
ProcID : : genNodeDomain ( )
{
auto p = nodeName_ . find ( ' : ' ) ;
return p = = string : : npos ? string { }
: string { nodeName_ . substr ( 0 , p ) } ;
}
string
ProcID : : genQualifier ( )
{
std : : ostringstream buffer ;
if ( not isnil ( portQual_ ) )
buffer < < ' . ' < < portQual_ ;
return buffer . str ( ) ;
2024-11-04 23:56:16 +01:00
}
2024-11-03 23:58:25 +01:00
2024-11-04 23:56:16 +01:00
namespace { // Helper to access ProcID recursively
ProcID &
procID ( ProcNode & node )
{
REQUIRE ( not isnil ( watch ( node ) . ports ( ) ) ) ;
return watch ( node ) . ports ( ) . front ( ) . procID ;
}
}
2024-11-03 21:03:34 +01:00
2024-11-04 23:56:16 +01:00
string
ProcID : : genNodeSpec ( Leads & leads )
{
std : : ostringstream buffer ;
2025-01-11 22:17:07 +01:00
buffer < < nodeName_ ;
2024-11-04 23:56:16 +01:00
if ( 1 ! = leads . size ( ) )
buffer < < genSrcSpec ( leads ) ;
else
{ // single chain....
ProcNode & p { leads . front ( ) . get ( ) } ;
buffer < < " ◁— "
< < procID ( p ) . genNodeName ( ) // show immediate predecessor
< < procID ( p ) . genSrcSpec ( leads ) ; // and behind that recursively the source(s)
}
return buffer . str ( ) ;
}
string
ProcID : : genSrcSpec ( Leads & leads )
{
return isnil ( leads ) ? string { " -◎ " } // no leads => starting point itself is a source node
: " ┉┉{ "
+ util : : join (
explore ( leads )
. expandAll ( [ ] ( ProcNode & n ) { return explore ( watch ( n ) . leads ( ) ) ; } ) // depth-first expand all predecessors
. filter ( [ ] ( ProcNode & n ) { return watch ( n ) . isSrc ( ) ; } ) // but retain only leafs (≙ source nodes)
2025-01-11 22:17:07 +01:00
. transform ( [ ] ( ProcNode & n ) { return procID ( n ) . nodeName_ ; } ) // render the node-symbol of each src
2024-11-04 23:56:16 +01:00
. deduplicate ( ) ) // sort and deduplicate
+ " } " ;
}
/**
* @ return symbolic string with format ` NodeSymb - - < predecessorSpec > `
* @ remark connectivity information is abbreviated and foremost
* indicates the data source ( s )
*/
2024-11-02 23:54:41 +01:00
string
ProcNodeDiagnostic : : getNodeSpec ( )
{
2024-11-04 23:56:16 +01:00
REQUIRE ( not isnil ( ports ( ) ) ) ;
return ports ( ) . front ( ) . procID . genNodeSpec ( leads ( ) ) ;
2024-11-02 23:54:41 +01:00
}
HashVal
ProcNodeDiagnostic : : getNodeHash ( ) ///< @todo not clear yet if this has to include predecessor info
{
UNIMPLEMENTED ( " calculate an unique hash-key to designate this node " ) ;
}
2024-11-03 21:03:34 +01:00
/**
* @ return symbolic string with format ` NodeSymb [ . portQualifier ] ( inType [ / # ] [ , inType [ / # ] ] ) ( outType [ / # ] [ , outType [ / # ] ] [ > N ] ) `
* @ remark information presented here is passed - through from builder Level - 3 , based on semantic markup present there
*/
2024-11-02 23:54:41 +01:00
string
ProcNodeDiagnostic : : getPortSpec ( uint portIdx )
{
2024-11-03 21:03:34 +01:00
auto & p { n_ . wiring_ . ports } ;
2024-11-03 23:58:25 +01:00
return portIdx < p . size ( ) ? p [ portIdx ] . procID . genProcSpec ( )
: util : : FAILURE_INDICATOR ;
2024-11-02 23:54:41 +01:00
}
HashVal
ProcNodeDiagnostic : : getPortHash ( uint portIdx )
{
UNIMPLEMENTED ( " calculate an unique, stable and reproducible hash-key to identify the Turnout " ) ;
}
2024-06-23 19:40:39 +02:00
2025-01-08 16:39:00 +01:00
lib : : Several < PortRef >
PortDiagnostic : : srcPorts ( )
{
UNIMPLEMENTED ( " intrude into the Turnout and find out about source connectivity " ) ;
}
/**
* @ return the symbolic string representing this processing port ,
* as [ provided by Node - identification ] ( \ ref ProcID : : genProcSpec ( ) )
*/
string
PortDiagnostic : : getProcSpec ( )
{
2025-01-11 22:17:07 +01:00
return p_ . procID . genProcSpec ( ) ;
2025-01-08 16:39:00 +01:00
}
HashVal
PortDiagnostic : : getProcHash ( ) ///< @return as [calculated by Node-identification](\ref ProcID)
{
UNIMPLEMENTED ( " calculate an unique, stable and reproducible hash-key to identify the Turnout " ) ;
}
2024-06-23 19:40:39 +02:00
} } // namespace steam::engine