'Copyright Luke Ceddia
'SPDX-License-Identifier: Apache-2.0
'pratt.bm - Expression parser using the Pratt algorithm

'Expects: first token of expr
'Results: token after expression
'Note: the expression parser is greedy; it will only stop when it encounters
'      a token that cannot possibly be part of an expression.
function ps_expr
    $if DEBUG_PARSE_TRACE then
    debuginfo "Start expr"
    $end if
    ps_expr = pt_expr(0)
    $if DEBUG_PARSE_TRACE then
    debuginfo "Completed expr"
    $end if
end function

function pt_expr(rbp)
    t = tok_token
    content$ = tok_content$
    tok_advance
    left_node = nud(t, content$)
    while rbp < lbp(tok_token, tok_content$)
        t = tok_token
        content$ = tok_content$
        tok_advance
        left_node = led(t, content$, left_node)
    wend
    pt_expr = left_node
end function

'tok_token is positioned one after whatever token is.
'ps_ functions called from here usually need to be specially written
'to take their first token as an argument instead of from tok_token.
function nud(token, content$)
    select case token
    case TOK_NUMINT, TOK_NUMBASE, TOK_NUMDEC, TOK_NUMEXP, TOK_STRINGLIT
        node = ast_add_node(AST_CONSTANT)
        node->ref = ast_add_constant(token, content$, ps_opt_sigil)
    case TOK_OPAREN
        node = pt_expr(0)
        ps_consume TOK_CPAREN
    case TOK_DASH
        'Hardcoded hack to change TOK_DASH into TOK_NEGATIVE
        token = TOK_NEGATIVE
        goto negative_hack
    case TOK_UNKNOWN
        'Implicit variable definitions
        node = ps_simple_variable_p(token, content$)
    case else
        negative_hack:
        select case token->stype
        case SYM_FUNCTION
            node = ps_funccall_p(token)
        case SYM_VARIABLE
            node = ps_lvalue_p(token, content$)
        case SYM_PREFIX
            node = ast_add_node(AST_CALL)
            node->ref = token
            expr = pt_expr(token->precedence)
            if type_is_lvalue(expr) then candidate_flags = TYPE_BYREF
            candidate$ = type_sigt_create$(TYPE_ANY)
            candidate$ = type_sigt_add_arg$(candidate$, type_of_expr(expr), flags)
            matching_sig = type_find_sig_match(token, candidate$)
            if matching_sig = 0 then ps_error "Cannot find matching type signature"
            cast = ast_add_cast(expr, type_sig_argtype(matching_sig, 1))
            node->attach(cast)
            node->ref2 = matching_sig
        case else
            ps_error "Unexpected " + tok_human_readable$(token)
        end select
    end select
    nud = node
end function

function lbp(token, content$)
    select case token
    case is < 0
        ps_error "Unexpected literal " + content$
    case TOK_CPAREN
        lbp = 0
    case else
        select case token->stype
        case SYM_INFIX
            lbp = token->precedence
        case else
            'We've hit something that isn't part if the expression,
            'time to finish.
            lbp = 0
        end select
    end select
end function

function led(token, content$, left_node)
    'content$ is not used but might be useful one day.
    'This next line stops the warning about unused variables.
    content$ = content$
    node = ast_add_node(AST_CALL)
    node->ref = token
    select case token->stype
    case SYM_INFIX
        if token->associativity = 0 then 'Left-associative
            right_node = pt_expr(token->precedence)
        else 'right-associative
            right_node = pt_expr(token->precedence - 1)
        end if
        candidate$ = type_sigt_create$(TYPE_ANY)
        if type_is_lvalue(left_node) then candidate_flags = TYPE_BYREF
        candidate$ = type_sigt_add_arg$(candidate$, type_of_expr(left_node), flags)
        if type_is_lvalue(right_node) then candidate_flags = TYPE_BYREF
        candidate$ = type_sigt_add_arg$(candidate$, type_of_expr(right_node), flags)
        matching_sig = type_find_sig_match(token, candidate$)
        if matching_sig = 0 then ps_error "Cannot find matching type signature"
        cast = ast_add_cast(left_node, type_sig_argtype(matching_sig, 1))
        node->attach(cast)
        cast = ast_add_cast(right_node, type_sig_argtype(matching_sig, 2))
        node->attach(cast)
        node->ref2 = matching_sig
    case else
        ps_error "Unexpected led " + tok_human_readable$(token)
    end select
    led = node
end function
