static_assert( __cplusplus >= 2023'02 ); #include "StackMachine.h" #include namespace { using namespace std::literals::string_literals; using namespace std::literals::string_view_literals; namespace C { constexpr bool debug= false; constexpr bool debugCompiledCall= C::debug or false; } void breakpoint() {} } namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m { StackMachine::StackMachine( std::ostream &output ) : output( output ) { } struct StackMachine::TokenHolder { std::unordered_map< std::string_view, std::vector< Token > >::iterator where; }; void StackMachine::compile( std::unordered_map< std::string_view, std::vector< Token > >::iterator def ) { tokenHolders.push_back( std::make_unique< TokenHolder >( TokenHolder{ def } ) ); if( C::debug ) { std::cerr << "Replacing token " << std::get< std::string >( *current ) << " (" << def->first << ") with compiled " << "address " << (void * ) tokenHolders.back()->where->second.data() << std::endl;; } *current= tokenHolders.back(); } void StackMachine::run() { while( not tokenStack.empty() and tokenStack.back().hasNext() ) { const bool inConditional= not conditionals.empty(); auto token= next(); if( std::holds_alternative< std::shared_ptr< TokenHolder > >( token ) ) { if( inConditional and currentState != conditionals.back() ) { breakpoint(); continue; } // If compiled, just go directly there. if( C::debug ) std::cerr << " Executing to compiled token: " << (void *) std::get< std::shared_ptr< TokenHolder > >( token ).get() << std::endl; if( C::debugCompiledCall ) { std::cerr << "\n\n\n\n===============================================================================================" << std::endl; std::cerr << "Top of stack is: " << std::visit( []< typename T >( const T &value ) { return boost::lexical_cast< std::string >( value ); }, peek() ) << std::endl; const auto target= std::get< std::shared_ptr< TokenHolder > >( token )->where; std::cerr << "The token sequence to execute (for function " << target->first << ") is:" << std::endl; const auto &def= target->second; std::transform( begin( def ), end( def ), std::ostream_iterator< std::string >{ std::cerr, "\n" }, []( const auto &element ) { return std::visit ( []< typename T >( const T &val ) { if constexpr( std::is_same_v< T, std::string > ) return val; else return boost::lexical_cast< std::string >( (void *) val->where->second.data() ) + ": " + std::string{ val->where->first }; }, element ); } ); } tokenStack.emplace_back( std::get< std::shared_ptr< TokenHolder > >( token )->where->second ); continue; } else runWord( std::get< std::string >( token ) ); if( C::debug ) std::cerr << "After processing stack is now: " << std::endl; if( C::debug ) for( const auto &element: stack ) { std::visit ( [&]( const auto &thing ) { std::cerr << thing << std::endl; }, element ); } } if( C::debug ) std::cerr << "Run done with stack at size: " << stack.size() << std::endl; } const StackMachine::Token & StackMachine::next() { if( tokenStack.empty() ) { throw std::runtime_error{ "FATAL: Token required, no more tokens left." }; } auto &rv= tokenStack.back().next(); current= &rv; while( not tokenStack.empty() and not tokenStack.back().hasNext() ) tokenStack.pop_back(); return rv; } void StackMachine::runWord( const std::string_view word ) { if( C::debug ) std::cerr << "Processing token: " << word << std::endl; if( C::debug ) std::cerr << "Conditional depth is: " << conditionals.size() << std::endl; if( C::debug ) { std::cerr << "The following cached named functions exist: " << std::endl; for( const auto &name: wordNames ) { std::cerr << name << std::endl; } std::cerr << "The following named functions exist: " << std::endl; for( const auto &[ name, _ ]: words ) { std::cerr << name << std::endl; } } const bool inConditional= not conditionals.empty(); if( false ); else if( word.empty() ); else if( word == "}"sv ) { if( not definition.has_value() ) { throw std::runtime_error{ "Stray end of function definition" }; } if( C::debug ) std::cerr << "Definition of " << definition.value() << " is done. It is: " << std::endl; if( C::debug ) std::transform( begin( words.at( definition.value() ) ), end( words.at( definition.value() ) ), std::ostream_iterator< std::string_view >{ std::cerr, "\n" }, []( const auto &element ) { return std::visit ( []< typename T >( const T &val ) -> std::string_view { if constexpr( std::is_same_v< T, std::string > ) return val; else return val->where->first; }, element ); } ); definition= std::nullopt; } else if( definition.has_value() ) { if( C::debug ) std::cerr << "Adding word: " << word << " to function definition: " << definition.value() << std::endl; words[ definition.value() ].push_back( std::string{ word } ); } else if( inConditional and word == "@else"sv and currentState != Else ) { currentState= Else; } else if( inConditional and word == "@endif"sv ) { conditionals.pop_back(); currentState= resumeConditionals.back(); resumeConditionals.pop_back(); } else if( inConditional and currentState != conditionals.back() ) { if( word == "@if"sv ) { conditionals.push_back( Skipped ); resumeConditionals.push_back( currentState ); currentState= If; } else ; // We skip the statement. } else if( word == "@else"sv ) { throw std::runtime_error{ "Syntax error: unexpected `@else`" }; } else if( word == "@endif"sv ) { throw std::runtime_error{ "Syntax error: unexpected `@endif`" }; } else if( word == "@dup"sv ) { auto val= peek(); push( std::move( val ) ); } else if( word == "@swap"sv ) { auto a= pop(); auto b= pop(); push( std::move( a ) ); push( std::move( b ) ); } else if( word == "@drop"sv ) { std::ignore= pop(); } else if( word == "@not"sv ) { auto val= pop< Integer >(); push( val == 0 ? 1 : 0 ); } else if( word == "@i_inc"sv ) { push( pop< Integer >() + 1 ); } else if( word == "@i_dec"sv ) { push( pop< Integer >() - 1 ); } else if( word == "@i_add"sv ) { const auto lhs= pop< Integer >(); const auto rhs= pop< Integer >(); push( lhs + rhs ); } else if( word == "@i_sub"sv ) { const auto lhs= pop< Integer >(); const auto rhs= pop< Integer >(); push( lhs - rhs ); } else if( word == "@i_mul"sv ) { const auto lhs= pop< Integer >(); const auto rhs= pop< Integer >(); push( lhs * rhs ); } else if( word == "@i_div"sv ) { const auto lhs= pop< Integer >(); const auto rhs= pop< Integer >(); push( lhs / rhs ); } else if( word == "@print"sv ) { output << pop< std::string >() << std::endl; } else if( word == "@if"sv ) { const auto val= pop< Integer >(); conditionals.push_back( val ? If : Else ); resumeConditionals.push_back( currentState ); currentState= If; } else if( word == "{"sv ) { if( definition.has_value() ) throw std::runtime_error{ "Nested definitions not supported." }; wordNames.push_back( pop< std::string >() ); definition= wordNames.back(); words[ wordNames.back() ]= {}; if( C::debug ) std::cerr << "Starting function definition for " << definition.value() << std::endl; } else if( word.at( 0 ) == '@' ) { const auto invoke= word.substr( 1 ); if( not words.contains( invoke ) ) { throw std::runtime_error{ "Unknown keyword: `"s + std::string{ word } + "`" }; } auto found= words.find( invoke ); tokenStack.emplace_back( found->second ); compile( found ); // Memoize it for next time. } else { push( word ); } } template< typename T > T StackMachine::pop() { if( std::holds_alternative< T >( peek() ) ) return std::get< T >( pop() ); else if( std::holds_alternative< Integer >( peek() ) ) return boost::lexical_cast< T >( std::get< Integer >( pop() ) ); else if( std::holds_alternative< Long >( peek() ) ) return boost::lexical_cast< T >( std::get< Long >( pop() ) ); else return boost::lexical_cast< T >( std::get< std::string >( pop() ) ); } StackMachine::Value StackMachine::pop() { const auto rv= std::move( peek() ); stack.pop_back(); return rv; } StackMachine::Value & StackMachine::peek() { if( stack.empty() ) throw std::runtime_error{ "FATAL: No more elements in stack." }; return stack.back(); } void StackMachine::push( Value element ) { stack.push_back( std::move( element ) ); } void StackMachine::push( std::string element ) { stack.push_back( std::move( element ) ); } void StackMachine::push( Integer element ) { stack.push_back( std::move( element ) ); } void StackMachine::push( Long element ) { stack.push_back( std::move( element ) ); } template< typename T > void StackMachine::push( const T &t ) { if constexpr( std::is_same_v< Integer, T > ) push( t ); else if constexpr( std::is_same_v< Long, T > ) push( t ); else if constexpr( std::is_same_v< std::string, T > ) push( t ); else push( boost::lexical_cast< std::string >( t ) ); } void StackMachine::loadProgram( std::vector< std::string > tokens ) { this->tokens.clear(); std::copy( begin( tokens ), end( tokens ), back_inserter( this->tokens ) ); tokenStack.clear(); tokenStack.emplace_back( this->tokens ); } } int main() { std::vector< std::string > program; while( std::cin ) { std::string token; std::cin >> token; program.push_back( std::move( token ) ); } using Dillo::JavaScriptForge::StackMachine; StackMachine machine; machine.loadProgram( std::move( program ) ); machine.run(); }