It seems slower, though. I'm preserving it in this branch, for now.
389 lines
9.8 KiB
C++
389 lines
9.8 KiB
C++
static_assert( __cplusplus >= 2023'02 );
|
|
|
|
#include "StackMachine.h"
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
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();
|
|
}
|