Goose  Artifact [1a0b6ffda2]

Artifact 1a0b6ffda29f6e00a151e75a182646e97655345d148d86ce37216d9ec791407f:

  • File bs/builtins/operators/dollar.cpp — part of check-in [0db147f117] at 2024-09-15 20:24:31 on branch cir-ssa-refactor — Add clang format settings, reformat everything (user: achavasse size: 5918)

#include "builtins/builtins.h"
#include "precedence.h"

using namespace goose;
using namespace goose::eir;
using namespace goose::parse;
using namespace goose::builtins;

// Dollar is a low level operator: it doesn't expect a value rhs operand,
// but just an identifier eir 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
{
    Value BuildOrRetrieveTVar(
        const Context& c, LocationId locationId, StringId name, bool forwarding )
    {
        if( name == "_"_sid )
            return forwarding ? ToValue( TTVar( "_"_sid ) ) : ToValue( TVar( "_"_sid ) );

        // Template name bindings are stored with a "$$" prefix so they don't
        // collide with regular names.
        auto captureIdentity = AppendToVectorTerm( c.identity(),
            TERM( forwarding ? DecorateTTVarName( name ) : DecorateTVarName( 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 *EIRToValue( result );

            case sema::Env::Status::AmbiguousMatch:
                DiagnosticsManager::GetInstance().emitErrorMessage( locationId,
                    format( "unexpected ambiguous match when resolving '${}'.", name.str() ) );
                return PoisonValue();

            default:
                break;
        }

        // No bound value was found: construct a TVar or a TTVar.
        return forwarding ? ToValue( TTVar( name ) ) : ToValue( TVar( name ) );
    }

    bool ParsePrefixDollarOperator( Parser& p, LocationId locationId, uint32_t prec )
    {
        auto nameTerm = p.resolver()->consumeUnresolved();
        if( !nameTerm )
        {
            DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                locationId, "expected an identifier after '$'.", 0 );
            return false;
        }

        const auto* name = get_if< StringId >( &nameTerm->first );
        if( !name )
        {
            DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                locationId, "expected an identifier after '$'.", 0 );
            return false;
        }

        const auto& c = p.context();
        auto val = BuildOrRetrieveTVar( c, nameTerm->second, *name, false );

        auto loc = Location::CreateSpanningLocation( locationId, nameTerm->second );
        val.setLocationId( loc );
        p.pushValue( move( val ) );
        return true;
    }

    bool ParsePrefixDollarDollarOperator( Parser& p, LocationId locationId, uint32_t prec )
    {
        auto nameTerm = p.resolver()->consumeUnresolved();
        if( !nameTerm )
        {
            DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                locationId, "expected an identifier after '$$'.", 0 );
            return false;
        }

        const auto* name = get_if< StringId >( &nameTerm->first );
        if( !name )
        {
            DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                locationId, "expected an identifier after '$$'.", 0 );
            return false;
        }

        const auto& c = p.context();
        auto val = BuildOrRetrieveTVar( c, nameTerm->second, *name, true );

        p.pushValue( move( val ) );
        return true;
    }

    optional< uint32_t > InfixDollarPrecedence( const Parser& p )
    {
        const auto& leftVal = p.peekLastValue();
        if( !leftVal )
            return nullopt;

        auto nameTerm = p.resolver()->lookAheadUnresolved();
        if( !nameTerm )
            return nullopt;

        if( !holds_alternative< StringId >( nameTerm->first ) )
            return nullopt;

        if( !builtins::IsType( p.context(), *leftVal ) && !builtins::IsTExpr( *leftVal ) )
            return nullopt;

        return precedence::Decl;
    }

    bool ParseInfixDollarOperator( Parser& p, LocationId locationId, uint32_t prec )
    {
        const auto& leftVal = p.peekLastValue();
        if( !leftVal )
            return false;

        if( !builtins::IsType( p.context(), *leftVal ) && !IsTExpr( *leftVal ) )
            return false;

        auto nameTerm = p.resolver()->consumeUnresolved();
        const auto* name = get_if< StringId >( &nameTerm->first );
        const auto& c = p.context();

        auto val = BuildOrRetrieveTVar( c, nameTerm->second, *name, 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 ) )
        {
            DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                nameTerm->second, "expected an unbound identifier after '$'." );
            return false;
        }

        auto loc = Location::CreateSpanningLocation( leftVal->locationId(), nameTerm->second );

        auto typeOrTExpr = ValueToEIR( *p.popValue() );
        auto tdecl = TDecl( move( typeOrTExpr ), *name );

        p.pushValue( ToValue( move( tdecl ) ).setLocationId( loc ) );
        return true;
    }
} // namespace

namespace goose::builtins
{
    void SetupDollarOp( Env& e )
    {
        Rule r( ParsePrefixDollarOperator, InfixDollarPrecedence, ParseInfixDollarOperator );
        RegisterRule( e, "$"_sid, move( r ) );

        Rule r2( ParsePrefixDollarDollarOperator );
        RegisterRule( e, "$$"_sid, move( r2 ) );
    }
} // namespace goose::builtins