#include "builtins/builtins.h"
#include "precedence.h"
#include "builtins/helpers.h"
using namespace empathy;
using namespace empathy::ir;
using namespace empathy::parse;
using namespace empathy::builtins;
// Dollar is a low level operator: it doesn't expect a value rhs operand,
// but just an identifier ir instruction.
// So we can't use the convenient operator wrapper rule.
//
// Also, it works both as a prefix operator to construct a TVar,
// and as an infix operator (with a decl or TEXpr as the left value) to
// construct a Decl of the form "<type> $FOO" or a TDecl of the form
// "<TExpr> $FOO" (for instance $T $FOO)
namespace
{
optional< Value > BuildOrRetrieveTVar( const Context& c, const StringId& name )
{
// Template name bindings are stored with a "$$" prefix so they don't
// collide with regular names.
auto captureIdentity = AppendToVectorTerm( c.identity(),
TERM( "$$"_sid ), TERM( name ) );
// Look up the TVar name. If it exists, then we're inside of a template function
// body, refering to a bound TVar, so we just return the stored value.
// Otherwise, we're in a template declaration and have to construct a new TVar.
Term result;
switch( c.env()->retrieveValue( captureIdentity, c.identity(), result ) )
{
case sema::Env::Status::Success:
return ValueFromIRExpr( result );
case sema::Env::Status::AmbiguousMatch:
cout << "unexpected ambiguous match when resolving '$" << name << "'.\n";
return nullopt;
}
// No bound value was found: construct a TVar.
return ToValue( TVar( name ) );
}
bool ParsePrefixDollarOperator( Parser& p, const Term& t, uint32_t prec )
{
auto nameTerm = p.resolver()->consumeUnresolved();
if( !nameTerm )
{
cout << t.location() << ": expected an identifier after '$'.\n";
return false;
}
const auto* name = get_if< StringId >( &nameTerm->content() );
if( !name )
{
cout << nameTerm->location() << ": expected an identifier after '$'.\n";
return false;
}
const auto& c = p.resolver()->context();
if( auto val = BuildOrRetrieveTVar( c, *name ) )
{
p.pushSimpleValue( move( *val ) );
return true;
}
return false;
}
optional< uint32_t > InfixDollarPrecedence( const Parser& p, const Term& t )
{
const auto& leftVal = p.peekLastValue();
if( !leftVal )
return nullopt;
auto nameTerm = p.resolver()->lookAheadUnresolved();
if( !nameTerm )
return nullopt;
if( !holds_alternative< StringId >( nameTerm->content() ) )
return nullopt;
if( !leftVal->isType() && !builtins::IsTExpr( *leftVal ) )
return nullopt;
// The infix dollar operator is a syntactic shortcut meant to behave like we are applying
// a TExpr to a type or another TExpr (just like a regular decl), so it have the application precedence.
return precedence::Application;
}
bool ParseInfixDollarOperator( Parser& p, const Term& t, uint32_t prec )
{
const auto& leftVal = p.peekLastValue();
if( !leftVal )
return false;
if( !leftVal->isType() && !IsTExpr( *leftVal ) )
return false;
auto nameTerm = p.resolver()->consumeUnresolved();
const auto* name = get_if< StringId >( &nameTerm->content() );
const auto& c = p.resolver()->context();
auto val = BuildOrRetrieveTVar( c, *name );
if( !val )
return false;
// If the TVar was already bound, we may end up with something in val
// that isn't a TVar, which is an error.
if( !IsTVar( *val ) )
{
cout << nameTerm->location() << ": expected an unbound identifier after '$'.\n";
return false;
}
auto typeOrTExpr = ValueToIRExpr( *p.popValue() );
auto tdecl = builtins::BuildTDecl( c, move( typeOrTExpr ), *name );
assert( tdecl );
p.pushSimpleValue( ToValue( move( *tdecl ) ) );
return true;
}
}
namespace empathy::builtins
{
void SetupDollarOp( Env& e )
{
Rule r( ParsePrefixDollarOperator, InfixDollarPrecedence, ParseInfixDollarOperator );
RegisterRule( e, "$"_sid, move( r ) );
}
}