#include "generated/vn_scene2.meta.c"


////////////////////////////////
//~ sixten: Scene Message Functions
static void S2_PushMessage(arena *Arena, scene2_message_list *List, token Token, string Message)
{
    scene2_message *Node = PushStruct(Arena, scene2_message);
    Node->Message = PushString(Arena, Message);
    Node->Token = Token;
    QueuePush(List->First, List->Last, Node);
    List->Count += 1;
}
static void S2_PushMessageFV(arena *Arena, scene2_message_list *List, token Token, char *Format, va_list Arguments)
{
    scene2_message *Node = PushStruct(Arena, scene2_message);
    Node->Message = PushFormatVariadic(Arena, Format, Arguments);
    Node->Token = Token;
    QueuePush(List->First, List->Last, Node);
    List->Count += 1;
}
static void S2_PushMessageF(arena *Arena, scene2_message_list *List, token Token, char *Format, ...)
{
    va_list Arguments;
    va_start(Arguments, Format);
    S2_PushMessageFV(Arena, List, Token, Format, Arguments);
    va_end(Arguments);
}
static scene2_message_list S2_CopyMessageList(arena *Arena, scene2_message_list *Source)
{
    scene2_message_list List = {};
    for(scene2_message *Message = Source->First; Message != 0; Message = Message->Next)
    {
        S2_PushMessage(Arena, &List, Message->Token, Message->Message);
    }
    return(List);
}
static void S2_ConcatMessageList(arena *Arena, scene2_message_list *Dest, scene2_message_list *Source)
{
    for(scene2_message *Message = Source->First; Message != 0; Message = Message->Next)
    {
        S2_PushMessage(Arena, Dest, Message->Token, Message->Message);
    }
}


////////////////////////////////
//~ sixten: Scene Action Functions
static void S2_SceneActionPush(scene2_action_list *List, scene2_action *Action)
{
    QueuePush(List->First, List->Last, Action);
    List->Count += 1;
}


////////////////////////////////
//~ sixten: Scene Parsing Helpers
static token S2_Token(scene2_compiler *Compiler)
{
    token Token = {};
    if(Compiler->At >= 0 && Compiler->At < Compiler->Tokens.Count)
    {
        Token = Compiler->Tokens.Tokens[Compiler->At];
    }
    return(Token);
}
static token S2_NextToken(scene2_compiler *Compiler)
{
    token Token = {};
    if(Compiler->At+1 >= 0 && Compiler->At+1 < Compiler->Tokens.Count)
    {
        Token = Compiler->Tokens.Tokens[Compiler->At+1];
    }
    return(Token);
}
static string S2_StringFromToken(scene2_compiler *Compiler, token Token)
{
    string Result = Substring(Compiler->Source, Intersection(Token.Range, Range1S64(0, Compiler->Source.Count)));
    return(Result);
}
static string S2_TokenString(scene2_compiler *Compiler)
{
    string Result = S2_StringFromToken(Compiler, S2_Token(Compiler));
    return(Result);
}
static void S2_ParseError(scene2_compiler *Compiler, token Token, string Message)
{
    S2_PushMessage(Compiler->Arena, &Compiler->Messages, Token, Message);
    Compiler->InPanicMode = true;
}
static token S2_Consume(scene2_compiler *Compiler, token_kind Expected, string Message)
{
    if(S2_Token(Compiler).Kind != Expected)
    {
        S2_ParseError(Compiler, S2_Token(Compiler), Message);
    }
    token Result = Compiler->Tokens.Tokens[Compiler->At];
    S2_Advance(Compiler);
    return(Result);
}
static void S2_Advance(scene2_compiler *Compiler)
{
    Compiler->At += 1;
}
static scene2_parse_rule S2_ParseRuleFromToken(scene2_compiler *Compiler, token Token)
{
    scene2_parse_rule Result = {};
    switch(Token.Kind)
    {
        case TokenKind_ParenthesisOpen: { Result = { S2_ParseGrouping, 0,              S2_Precedence_None       }; } break;
        case TokenKind_Bang:            { Result = { S2_ParseUnary,    0,              S2_Precedence_None       }; } break;
        case TokenKind_Minus:           { Result = { S2_ParseUnary,    S2_ParseBinary, S2_Precedence_Term       }; } break;
        case TokenKind_Plus:            { Result = { 0,                S2_ParseBinary, S2_Precedence_Term       }; } break;
        case TokenKind_Star:            { Result = { 0,                S2_ParseBinary, S2_Precedence_Factor     }; } break;
        case TokenKind_Slash:           { Result = { 0,                S2_ParseBinary, S2_Precedence_Factor     }; } break;
        case TokenKind_EqualEqual:      { Result = { 0,                S2_ParseBinary, S2_Precedence_Equality   }; } break;
        case TokenKind_BangEqual:       { Result = { 0,                S2_ParseBinary, S2_Precedence_Equality   }; } break;
        case TokenKind_Greater:         { Result = { 0,                S2_ParseBinary, S2_Precedence_Comparison }; } break;
        case TokenKind_GreaterEqual:    { Result = { 0,                S2_ParseBinary, S2_Precedence_Comparison }; } break;
        case TokenKind_Less:            { Result = { 0,                S2_ParseBinary, S2_Precedence_Comparison }; } break;
        case TokenKind_LessEqual:       { Result = { 0,                S2_ParseBinary, S2_Precedence_Comparison }; } break;
        case TokenKind_False:           { Result = { S2_ParseLiteral,  0,              S2_Precedence_None       }; } break;
        case TokenKind_True:            { Result = { S2_ParseLiteral,  0,              S2_Precedence_None       }; } break;
        case TokenKind_Numeric:         { Result = { S2_ParseNumber,   0,              S2_Precedence_None       }; } break;
        case TokenKind_StringLiteral:   { Result = { S2_ParseString,   0,              S2_Precedence_None       }; } break;
        case TokenKind_Identifier:      { Result = { S2_ParseVariable, 0,              S2_Precedence_None       }; } break;
        case TokenKind_Call:            { Result = { S2_ParseCall,     0,              S2_Precedence_None       }; } break;
        default: {} break;
    }
    return(Result);
}
static scene2_instruction S2_Instruction(scene2_opcode Opcode)
{
    scene2_instruction Result = {};
    Result.Opcode = Opcode;
    return(Result);
}
static scene2_instruction S2_Instruction(scene2_opcode Opcode, u64 Arg)
{
    scene2_instruction Result = {};
    Result.Opcode = Opcode;
    Result.Arg = Arg;
    return(Result);
}
static scene2_instruction S2_Instruction(scene2_opcode Opcode, r64 Number)
{
    scene2_instruction Result = {};
    Result.Opcode = Opcode;
    Result.Number = Number;
    return(Result);
}
static scene2_instruction S2_Instruction(scene2_opcode Opcode, u32 Arg1, u32 Arg2)
{
    scene2_instruction Result = {};
    Result.Opcode = Opcode;
    Result.Arg1 = Arg1;
    Result.Arg2 = Arg2;
    return(Result);
}
static scene2_instruction S2_Instruction(scene2_opcode Opcode, range1_s64 Range)
{
    Assert(0 <= Range.Min && Range.Min <= U32_Max);
    Assert(0 <= Range.Max && Range.Max <= U32_Max);
    scene2_instruction Result = {};
    Result.Opcode = Opcode;
    Result.Min = Range.Min;
    Result.Max = Range.Max;
    return(Result);
}
static scene2_instruction *S2_Emit(scene2_compiler *Compiler, scene2_instruction Instruction)
{
    scene2_instruction_chunk *Chunk = Compiler->LastChunk;
    if(!Chunk || Chunk->InstructionsUsed >= ArrayCount(Chunk->Instructions))
    {
        Chunk = PushStruct(Compiler->Arena, scene2_instruction_chunk);
        QueuePush(Compiler->FirstChunk, Compiler->LastChunk, Chunk);
    }
    scene2_instruction *Result = Chunk->Instructions + Chunk->InstructionsUsed;
    Chunk->InstructionsUsed += 1;
    Compiler->InstructionPointer += 1;
    *Result = Instruction;
    return(Result);
}


////////////////////////////////
//~ sixten: Scene Parsing Functions
static void S2_ParseTopLevel(scene2_compiler *Compiler)
{
    // sixten(NOTE): top-level -> { proc declaration }
    for(;Compiler->At < Compiler->Tokens.Count;)
    {
        switch(S2_Token(Compiler).Kind)
        {
            case TokenKind_Proc:
            {
                S2_ParseProc(Compiler);
            } break;
            
            default:
            {
                S2_ParseError(Compiler, S2_Token(Compiler), StrLit("Expected top level delaration (proc)"));
                S2_Advance(Compiler);
            } break;
        }
    }
}

static void S2_ParseProc(scene2_compiler *Compiler)
{
    // sixten(NOTE): proc -> 'proc' name { statement } 'end'
    
    S2_Advance(Compiler); // sixten: skip 'proc'
    token NameToken = S2_Consume(Compiler, TokenKind_Identifier, StrLit("Expected name after 'proc'."));
    
    scene2_proc_node *Node = PushStruct(Compiler->Arena, scene2_proc_node);
    QueuePush(Compiler->ProcList.First, Compiler->ProcList.Last, Node);
    Compiler->ProcList.Count += 1;
    Node->Info.InstructionBegin = Compiler->InstructionPointer;
    Node->Info.Name = S2_StringFromToken(Compiler, NameToken);
    
    S2_ParseBlock(Compiler);
    
    Node->Info.InstructionEnd = Compiler->InstructionPointer;
}

static void S2_ParseBlock(scene2_compiler *Compiler)
{
    // sixten: block -> { statement } 'end'
    S2_Emit(Compiler, S2_Instruction(S2_Op_BeginBlock));
    
    b32 FoundEnd = false;
    for(;Compiler->At < Compiler->Tokens.Count;)
    {
        switch(S2_Token(Compiler).Kind)
        {
            case TokenKind_End:
            {
                S2_Advance(Compiler); // sixten: skip 'end'
                FoundEnd = true;
                goto Done;
            } break;
            
            default:
            {
                S2_ParseStatement(Compiler);
            } break;
        }
    }
    Done:;
    if(!FoundEnd)
    {
        S2_ParseError(Compiler, S2_Token(Compiler), StrLit("Expected 'end' after block."));
    }
    
    S2_Emit(Compiler, S2_Instruction(S2_Op_EndBlock));
}

static void S2_ParseStatement(scene2_compiler *Compiler)
{
    // sixten: statement -> if statement | while statement | let statetment | expression
    switch(S2_Token(Compiler).Kind)
    {
        case TokenKind_Wait:       { S2_ParseWaitStatement(Compiler);  } break;
        case TokenKind_If:         { S2_ParseIfStatement(Compiler);    } break;
        case TokenKind_While:      { S2_ParseWhileStatement(Compiler); } break;
        case TokenKind_Let:        { S2_ParseLetStatement(Compiler);   } break;
        case TokenKind_DollarSign: { S2_ParseShorthand(Compiler);      } break;
        default:                   { S2_ParseExpression(Compiler);     } break;
    }
    
    if(Compiler->InPanicMode)
    {
        for(;Compiler->At < Compiler->Tokens.Count;)
        {
            if(S2_Token(Compiler).Kind == TokenKind_End)
            {
                break;
            }
            else
            {
                S2_Advance(Compiler);
            }
        }
        Compiler->InPanicMode = false;
    }
}

static void S2_ParseWaitStatement(scene2_compiler *Compiler)
{
    S2_Advance(Compiler); // sixten: skip 'wait'
    S2_Emit(Compiler, S2_Instruction(S2_Op_Wait));
}

static void S2_ParseIfStatement(scene2_compiler *Compiler)
{
    S2_Advance(Compiler); // sixten: skip 'if'
    S2_ParseExpression(Compiler);
    S2_Emit(Compiler, S2_Instruction(S2_Op_Not)); // sixten: we only jump if the condition is _NOT_ satisfied.
    scene2_instruction *JumpInstruction = S2_Emit(Compiler, S2_Instruction(S2_Op_CondJump));
    u64 OffsetBefore = Compiler->InstructionPointer;
    S2_Consume(Compiler, TokenKind_Then, StrLit("Expected 'then' after if-statement"));
    S2_ParseBlock(Compiler);
    u64 JumpOffset = Compiler->InstructionPointer - OffsetBefore;
    JumpInstruction->Arg = JumpOffset;
}

static void S2_ParseWhileStatement(scene2_compiler *Compiler)
{
    S2_Advance(Compiler); // sixten: skip 'while'
    u64 CondBeginOffset = Compiler->InstructionPointer;
    S2_ParseExpression(Compiler);
    S2_Emit(Compiler, S2_Instruction(S2_Op_Not)); // sixten: we only jump if the condition is _NOT_ satisfied.
    scene2_instruction *CondNotSatisfiedJump = S2_Emit(Compiler, S2_Instruction(S2_Op_CondJump));
    u64 OffsetJump = Compiler->InstructionPointer;
    S2_Consume(Compiler, TokenKind_Then, StrLit("Expected 'then' after while-statement"));
    S2_ParseBlock(Compiler);
    S2_Emit(Compiler, S2_Instruction(S2_Op_Jump, (u64)(CondBeginOffset - Compiler->InstructionPointer - 1)));
    u64 EndOffset = Compiler->InstructionPointer;
    CondNotSatisfiedJump->Arg = EndOffset - OffsetJump;
}

static void S2_ParseLetStatement(scene2_compiler *Compiler)
{
    S2_Advance(Compiler); // sixten: skip 'let'
    token LocalName = S2_Consume(Compiler, TokenKind_Identifier, StrLit("Expected variable name after 'let'"));
    if(S2_Token(Compiler).Kind == TokenKind_Equal)
    {
        S2_Advance(Compiler); // sixten: skip equals
        S2_ParseExpression(Compiler);
        
        S2_Emit(Compiler, S2_Instruction(S2_Op_SetLocal, LocalName.Range));
        S2_Emit(Compiler, S2_Instruction(S2_Op_SetVariable, LocalName.Range));
    }
    else
    {
        S2_Emit(Compiler, S2_Instruction(S2_Op_SetLocal, LocalName.Range));
    }
}

static void S2_ParseShorthand(scene2_compiler *Compiler)
{
    S2_Advance(Compiler); // sixten: skip '$'
    
    // sixten(NOTE): since we're using the parenthesis to check for a target specifier, we cannot
    // use grouping as the text to append to the textbox. Should never really be an issue, but it
    // does technically change how the languange should be documented.
    
    S2_Emit(Compiler, S2_Instruction(S2_Op_Call, S2_CallFunctionIndexFromString(StrLit("ctr_unset_all_talking"))));
    S2_Emit(Compiler, S2_Instruction(S2_Op_Call, S2_CallFunctionIndexFromString(StrLit("text_clear"))));
    
    if(S2_Token(Compiler).Kind == TokenKind_ParenthesisOpen)
    {
        S2_Advance(Compiler); // sixten: skip the '('
        S2_ParseExpression(Compiler); // sixten: emit the target
        S2_Emit(Compiler, S2_Instruction(S2_Op_DuplicateStack, 1llu));
        
        // sixten: emit the action
        if(S2_Token(Compiler).Kind == TokenKind_Comma)
        {
            S2_Advance(Compiler); // sixten: skip the ','
            
            token AssetToken = S2_Consume(Compiler, TokenKind_Identifier, StrLit("Expected asset name after shorthand & target specifier"));
            asset_id ID = A_AssetIDFromString(S2_StringFromToken(Compiler, AssetToken));
            if(ID == AssetID_None)
            {
                S2_ParseError(Compiler, AssetToken, StrLit("Unknown asset"));
            }
            S2_Emit(Compiler, S2_Instruction(S2_Op_PushNumber, (r64)ID));
        }
        else
        {
            S2_Emit(Compiler, S2_Instruction(S2_Op_PushNil));
        }
        S2_Consume(Compiler, TokenKind_ParenthesisClose, StrLit("Expetcted ')' after shorthand specifier"));
        S2_Emit(Compiler, S2_Instruction(S2_Op_Call, S2_CallFunctionIndexFromString(StrLit("ctr_set"))));
        S2_Emit(Compiler, S2_Instruction(S2_Op_Call, S2_CallFunctionIndexFromString(StrLit("ctr_set_talking"))));
    }
    
    S2_ParseExpression(Compiler);
    
    S2_Emit(Compiler, S2_Instruction(S2_Op_Call, S2_CallFunctionIndexFromString(StrLit("text_append"))));
    S2_Emit(Compiler, S2_Instruction(S2_Op_Wait));
}

static void S2_ParseExpression(scene2_compiler *Compiler)
{
    S2_ParsePrecedence(Compiler, S2_Precedence_Assignment);
}

static void S2_ParsePrecedence(scene2_compiler *Compiler, scene2_precedence Precedence)
{
    b32 CanAssign = Precedence <= S2_Precedence_Assignment;
    
    //- sixten: do prefix rule
    {
        scene2_parse_rule Rule = S2_ParseRuleFromToken(Compiler, S2_Token(Compiler));
        if(Rule.PrefixRule)
        {
            Rule.PrefixRule(Compiler, CanAssign);
        }
        else
        {
            S2_ParseError(Compiler, S2_Token(Compiler), StrLit("Expected expression."));
        }
    }
    
    //- sixten: do infix rule
    for(;;)
    {
        scene2_parse_rule Rule = S2_ParseRuleFromToken(Compiler, S2_Token(Compiler));
        if(Precedence <= Rule.Precedence)
        {
            Rule.InfixRule(Compiler, CanAssign);
        }
        else
        {
            break;
        }
    }
    
    if(CanAssign && S2_Token(Compiler).Kind == TokenKind_Equal)
    {
        S2_ParseError(Compiler, S2_Token(Compiler), StrLit("Invalid assignment target."));
    }
}

static void S2_ParseGrouping(scene2_compiler *Compiler, b32 CanAssign)
{
    UnusedVariable(CanAssign);
    S2_Advance(Compiler); // sixten: skip '('
    S2_ParseExpression(Compiler);
    S2_Consume(Compiler, TokenKind_ParenthesisClose, StrLit("Expected ')' after expression."));
}

static void S2_ParseUnary(scene2_compiler *Compiler, b32 CanAssign)
{
    UnusedVariable(CanAssign);
    scene2_operator Operator = S2_OperatorFromString(S2_TokenString(Compiler));
    S2_Advance(Compiler);
    S2_ParsePrecedence(Compiler, S2_Precedence_Unary);
    switch(Operator)
    {
        case S2_Operator_Minus: { S2_Emit(Compiler, S2_Instruction(S2_Op_Negate)); } break;
        case S2_Operator_Not:   { S2_Emit(Compiler, S2_Instruction(S2_Op_Not));    } break;
        InvalidDefaultCase;
    }
}

static void S2_ParseBinary(scene2_compiler *Compiler, b32 CanAssign)
{
    UnusedVariable(CanAssign);
    token Token = S2_Token(Compiler);
    scene2_operator Operator = S2_OperatorFromString(S2_TokenString(Compiler));
    scene2_parse_rule Rule = S2_ParseRuleFromToken(Compiler, Token);
    S2_Advance(Compiler); // sixten: skip operator
    S2_ParsePrecedence(Compiler, (scene2_precedence)(Rule.Precedence + 1));
    switch(Operator)
    {
        case S2_Operator_Add:                 { S2_Emit(Compiler, S2_Instruction(S2_Op_Add));                                                   } break;
        case S2_Operator_Minus:               { S2_Emit(Compiler, S2_Instruction(S2_Op_Subtract));                                              } break;
        case S2_Operator_Multiply:            { S2_Emit(Compiler, S2_Instruction(S2_Op_Multiply));                                              } break;
        case S2_Operator_Divide:              { S2_Emit(Compiler, S2_Instruction(S2_Op_Divide));                                                } break;
        case S2_Operator_Equals:              { S2_Emit(Compiler, S2_Instruction(S2_Op_Equals));                                                } break;
        case S2_Operator_NotEquals:           { S2_Emit(Compiler, S2_Instruction(S2_Op_Equals));  S2_Emit(Compiler, S2_Instruction(S2_Op_Not)); } break;
        case S2_Operator_Greater:             { S2_Emit(Compiler, S2_Instruction(S2_Op_Greater));                                               } break;
        case S2_Operator_GreaterThanOrEquals: { S2_Emit(Compiler, S2_Instruction(S2_Op_Less));    S2_Emit(Compiler, S2_Instruction(S2_Op_Not)); } break;
        case S2_Operator_Less:                { S2_Emit(Compiler, S2_Instruction(S2_Op_Less));                                                  } break;
        case S2_Operator_LessThanOrEquals:    { S2_Emit(Compiler, S2_Instruction(S2_Op_Greater)); S2_Emit(Compiler, S2_Instruction(S2_Op_Not)); } break;
        InvalidDefaultCase;
    }
}

static void S2_ParseVariable(scene2_compiler *Compiler, b32 CanAssign)
{
    token VariableToken = S2_Consume(Compiler, TokenKind_Identifier, StrLit("Expected identifier."));
    if(CanAssign && S2_Token(Compiler).Kind == TokenKind_Equal)
    {
        S2_Advance(Compiler);
        S2_ParseExpression(Compiler);
        S2_Emit(Compiler, S2_Instruction(S2_Op_SetVariable, VariableToken.Range)) ;
    }
    else
    {
        S2_Emit(Compiler, S2_Instruction(S2_Op_PushVariable, VariableToken.Range)) ;
    }
}

static void S2_ParseCall(scene2_compiler *Compiler, b32 CanAssign)
{
    UnusedVariable(CanAssign);
    S2_Advance(Compiler); // sixten: skip 'call'
    token NameToken = S2_Consume(Compiler, TokenKind_Identifier, StrLit("Expected function name."));
    S2_Consume(Compiler, TokenKind_ParenthesisOpen, StrLit("Expected '(' after function name"));
    
    //- sixten: parse arguments
    token Token = S2_Token(Compiler);
    if(Token.Kind != TokenKind_ParenthesisClose)
    {
        for(;Compiler->At < Compiler->Tokens.Count;)
        {
            S2_ParseExpression(Compiler);
            Token = S2_Token(Compiler);
            if(Token.Kind == TokenKind_ParenthesisClose)
            {
                break;
            }
            else
            {
                S2_Consume(Compiler, TokenKind_Comma, StrLit("Expected ',' or ')'."));
            }
        }
    }
    S2_Advance(Compiler); // sixten: skip ')'
    
    S2_Emit(Compiler, S2_Instruction(S2_Op_Call, S2_CallFunctionIndexFromString(S2_StringFromToken(Compiler, NameToken))));
}

static void S2_ParseLiteral(scene2_compiler *Compiler, b32 CanAssign)
{
    UnusedVariable(CanAssign);
    token LiteralToken = S2_Token(Compiler);
    switch(LiteralToken.Kind)
    {
        case TokenKind_False: { S2_Emit(Compiler, S2_Instruction(S2_Op_PushBool, 0ull)); } break;
        case TokenKind_True:  { S2_Emit(Compiler, S2_Instruction(S2_Op_PushBool, 1ull)); } break;
        InvalidDefaultCase;
    }
    S2_Advance(Compiler); // sixten: skip the literal
}

static void S2_ParseNumber(scene2_compiler *Compiler, b32 CanAssign)
{
    UnusedVariable(CanAssign);
    token NumberToken = S2_Token(Compiler);
    S2_Advance(Compiler); // sixten: skip the numeric literal
    S2_Emit(Compiler, S2_Instruction(S2_Op_PushNumber, DoubleFromString(S2_StringFromToken(Compiler, NumberToken))));
}

static void S2_ParseString(scene2_compiler *Compiler, b32 CanAssign)
{
    UnusedVariable(CanAssign);
    token StringToken = S2_Token(Compiler);
    S2_Advance(Compiler); // sixten: skip the string literal
    S2_Emit(Compiler, S2_Instruction(S2_Op_PushString, Pad(StringToken.Range, -1))); // sixten(TODO): push the string to the stack
}

static scene2_proc_list S2_CopyProcList(arena *Arena, scene2_proc_list Source)
{
    scene2_proc_list Result = {};
    for(scene2_proc_node *Node = Source.First; Node != 0; Node = Node->Next)
    {
        scene2_proc_node *New = PushStruct(Arena, scene2_proc_node);
        New->Info.Name = PushString(Arena, Node->Info.Name);
        New->Info.InstructionBegin = Node->Info.InstructionBegin;
        New->Info.InstructionEnd = Node->Info.InstructionEnd;
        QueuePush(Result.First, Result.Last, New);
        Result.Count += 1;
    }
    Assert(Result.Count == Source.Count);
    return(Result);
}


////////////////////////////////
//~ sixten: Scene Runtime Helper Functions

static string S2_StringFromObject(arena *Arena, scene2_object Object)
{
    string Result = StrLit("unknown");
    switch(Object.Kind)
    {
        case S2_ObjectKind_Nil:    { Result = StrLit("nil"); } break;
        case S2_ObjectKind_Bool:   { Result = PushFormat(Arena, "%b", Object.As.Bool); } break;
        case S2_ObjectKind_Number:
        {
            if(Object.As.Number == Round(Object.As.Number))
            {
                Result = PushFormat(Arena, "%i", (s64)Object.As.Number);
            }
            else
            {
                Result = PushFormat(Arena, "%f", Object.As.Number);
            }
        } break;
        case S2_ObjectKind_String: { Result = PushString(Arena, Object.As.String); } break;
    }
    return(Result);
}

static void S2_StackPush(scene2_runtime *Runtime, scene2_object Object)
{
    if(Runtime->StackPointer < ArrayCount(Runtime->Stack))
    {
        Runtime->Stack[Runtime->StackPointer++] = Object;
    }
}

static scene2_object S2_StackPop(scene2_runtime *Runtime)
{
    scene2_object Result = {};
    if(Runtime->StackPointer > 0)
    {
        Result = Runtime->Stack[--Runtime->StackPointer];
    }
    return(Result);
}

static scene2_object S2_MakeNil(void)
{
    scene2_object Result = {};
    Result.Kind = S2_ObjectKind_Nil;
    return(Result);
}

static scene2_object S2_MakeBool(b32 Bool)
{
    scene2_object Result = {};
    Result.Kind = S2_ObjectKind_Bool;
    Result.As.Bool = Bool;
    return(Result);
}

static scene2_object S2_MakeNumber(r64 Number)
{
    scene2_object Result = {};
    Result.Kind = S2_ObjectKind_Number;
    Result.As.Number = Number;
    return(Result);
}

static scene2_object S2_MakeString(string String)
{
    scene2_object Result = {};
    Result.Kind = S2_ObjectKind_String;
    Result.As.String = String;
    return(Result);
}

static b32 S2_ObjectIsTrue(scene2_object Object)
{
    b32 Result = false;
    if(Object.Kind == S2_ObjectKind_Bool)
    {
        Result = Object.As.Bool;
    }
    else if(Object.Kind == S2_ObjectKind_Number && Object.As.Number != 0)
    {
        Result = true;
    }
    else if(Object.Kind == S2_ObjectKind_String)
    {
        Result = true;
    }
    return(Result);
}

static string S2_StringFromInstruction(compiled_scene2 *Compiled, scene2_instruction Instruction)
{
    string Result = Substring(Compiled->Source, Range1S64(Instruction.Min, Instruction.Max));
    return(Result);
}

static scene2_variable *S2_NewVariable(scene2_runtime *Runtime)
{
    scene2_variable *Result = Runtime->FirstFreeVariable;
    if(Result)
    {
        DLLRemove(Runtime->FirstFreeVariable, Runtime->LastFreeVariable, Result);
        ZeroStruct(Result);
    }
    else
    {
        Result = PushStruct(Runtime->VariableArena, scene2_variable);
    }
    return(Result);
}

static scene2_variable *S2_VariableFromString(scene2_runtime *Runtime, string Name)
{
    scene2_variable *Result = 0;
    
    //- sixten: check if the variable exists
    for(scene2_variable *Variable = Runtime->FirstGlobalVariable; Variable != 0; Variable = Variable->Next)
    {
        if(AreEqual(Name, Variable->Name))
        {
            Result = Variable;
            break;
        }
    }
    if(!Result)
    {
        Result = S2_NewVariable(Runtime);
        Result->Name = PushString(Runtime->VariableArena, Name);
        DLLInsertLast(Runtime->FirstGlobalVariable, Runtime->LastGlobalVariable, Result);
    }
    
    return(Result);
}


////////////////////////////////
//~ sixten: Scene Interface Functions

static compiled_scene2 S2_CompiledFromString(arena *Arena, string String)
{
    temp Scratch = GetScratch(&Arena, 1);
    
    //- sixten: string -> tokens
    tokenize_result TokenizeResult = T_TokenizeFromText(Scratch.Arena, String, T_IsIrregular);
    
    scene2_compiler Compiler = {};
    Compiler.Arena = Scratch.Arena;
    Compiler.Tokens = TokenizeResult.Tokens;
    Compiler.Source = String;
    
    //- sixten: tokens -> bytecode
    S2_ParseTopLevel(&Compiler);
    
    compiled_scene2 Result = {};
    {
        //- sixten: copy source
        Result.Source = PushString(Arena, String);
        
        //- sixten: copy messages
        Result.Messages = S2_CopyMessageList(Arena, &Compiler.Messages);
        
        //- sixten: copy instructions
        Result.InstructionCount = Compiler.InstructionPointer;
        Result.Instructions = PushArray(Arena, scene2_instruction, Result.InstructionCount);
        u64 InstructionIndex = 0;
        for(scene2_instruction_chunk *Chunk = Compiler.FirstChunk; Chunk != 0; Chunk = Chunk->Next)
        {
            Copy(Result.Instructions+InstructionIndex, Chunk->Instructions, Chunk->InstructionsUsed*sizeof(scene2_instruction));
            InstructionIndex += Chunk->InstructionsUsed;
        }
        
        //- sixten: copy procs
        Result.ProcList = S2_CopyProcList(Arena, Compiler.ProcList);
    }
    
    ReleaseScratch(Scratch);
    return(Result);
}

static compiled_scene2 S2_CopyCompiledScene(arena *Arena, compiled_scene2 *Source)
{
    compiled_scene2 Result = {};
    Result.Source = PushString(Arena, Source->Source);
    Result.Messages = S2_CopyMessageList(Arena, &Source->Messages);
    Result.InstructionCount = Source->InstructionCount;
    Result.Instructions = PushArray(Arena, scene2_instruction, Result.InstructionCount);
    Copy(Result.Instructions, Source->Instructions, Result.InstructionCount*sizeof(scene2_instruction));
    Result.ProcList = S2_CopyProcList(Arena, Source->ProcList);
    return(Result);
}

static void S2_SetCurrentProc(scene2_runtime *Runtime, scene2_proc_info Info)
{
    Runtime->CurrentProc = Info;
    Runtime->InstructionPointer = Info.InstructionBegin;
}
static scene2_proc_info S2_ProcFromName(compiled_scene2 *Compiled, string Name)
{
    scene2_proc_info Result = {};
    for(scene2_proc_node *Node = Compiled->ProcList.First; Node != 0; Node = Node->Next)
    {
        if(AreEqual(Node->Info.Name, Name))
        {
            Result = Node->Info;
            break;
        }
    }
    return(Result);
}

static void S2_Init(scene2_runtime *Runtime)
{
    Runtime->VariableArena = ArenaAlloc(4096, true, "Scene Runtime Variable Arena");
}

static scene2_run_result S2_Run(arena *Arena, scene2_runtime *Runtime, compiled_scene2 *Compiled, b32 PlayerAction)
{
    scene2_run_result Result = {};
    scene2_proc_info *ProcInfo = &Runtime->CurrentProc;
    for(;ProcInfo->InstructionBegin <= Runtime->InstructionPointer && Runtime->InstructionPointer < ProcInfo->InstructionEnd && Result.Messages.Count < 100;)
    {
        scene2_instruction Instruction = Compiled->Instructions[Runtime->InstructionPointer];
        switch(Instruction.Opcode)
        {
            case S2_Op_None:
            default:
            {
                if(S2_Op_None <= Instruction.Opcode && Instruction.Opcode < S2_Op_COUNT)
                {
                    S2_PushMessageF(Arena, &Result.Messages, T_EmptyToken, "Unimplemented opcode: %S (%S)", S2_InstructionNameLookup[Instruction.Opcode], S2_InstructionInfoLookup[Instruction.Opcode]);
                }
                else
                {
                    S2_PushMessageF(Arena, &Result.Messages, T_EmptyToken, "Unknown opcode: %i", Instruction.Opcode);
                }
            } break;
            case S2_Op_Call:
            {
                // sixten: find the binding
                scene2_call_function *Function = S2_CallBindingLookup[Instruction.Arg];
                if(Function == S2_Call_Stub)
                {
                    // sixten(TODO): do we error here??
                }
                Function(Runtime, Compiled);
            } break;
            case S2_Op_Wait:
            {
                if(PlayerAction)
                {
                    // sixten: "consume" the action
                    PlayerAction = false;
                }
                else
                {
                    goto Done;
                }
            } break;
            case S2_Op_Pop:
            {
                S2_StackPop(Runtime);
            } break;
            case S2_Op_PushBool:
            {
                S2_StackPush(Runtime, S2_MakeBool(Instruction.Arg));
            } break;
            case S2_Op_PushNumber:
            {
                S2_StackPush(Runtime, S2_MakeNumber(Instruction.Number));
            } break;
            case S2_Op_PushString:
            {
                S2_StackPush(Runtime, S2_MakeString(S2_StringFromInstruction(Compiled, Instruction)));
            } break;
            case S2_Op_PushNil:
            {
                S2_StackPush(Runtime, S2_MakeNil());
            } break;
            case S2_Op_PushVariable:
            {
                scene2_variable *Variable = S2_VariableFromString(Runtime, S2_StringFromInstruction(Compiled, Instruction));
                S2_StackPush(Runtime, Variable->Value);
            } break;
            case S2_Op_DuplicateStack:
            {
                temp Scratch = GetScratch();
                scene2_object *Stack = PushArrayNoClear(Scratch.Arena, scene2_object, Instruction.Arg);
                for(u64 Index = 0; Index < Instruction.Arg; Index += 1)
                {
                    Stack[Index] = S2_StackPop(Runtime);
                }
                for(u64 Index = 0; Index < 2*Instruction.Arg; Index += 1)
                {
                    S2_StackPush(Runtime, Stack[Index%Instruction.Arg]);
                }
                ReleaseScratch(Scratch);
            } break;
            case S2_Op_SetVariable:
            {
                scene2_object NewValue = S2_StackPop(Runtime);
                scene2_variable *Variable = S2_VariableFromString(Runtime, S2_StringFromInstruction(Compiled, Instruction));
                Variable->Value = NewValue;
            } break;
            case S2_Op_SetLocal:
            {
                InvalidCodepath;
            } break;
            case S2_Op_BeginBlock:
            {
            } break;
            case S2_Op_EndBlock:
            {
            } break;
            case S2_Op_Jump:
            {
                Runtime->InstructionPointer += Instruction.Arg;
            } break;
            case S2_Op_CondJump:
            {
                if(S2_ObjectIsTrue(S2_StackPop(Runtime)))
                {
                    Runtime->InstructionPointer += Instruction.Arg;
                }
            } break;
            case S2_Op_Add:
            {
                scene2_object Object1 = S2_StackPop(Runtime);
                scene2_object Object2 = S2_StackPop(Runtime);
                if(Object1.Kind == S2_ObjectKind_Number && Object2.Kind == S2_ObjectKind_Number)
                {
                    S2_StackPush(Runtime, S2_MakeNumber(Object1.As.Number+Object2.As.Number));
                }
                else
                {
                    S2_PushMessageF(Arena, &Result.Messages, T_EmptyToken, "Trying to add two incompatible types: %S & %S. Will return nil.", S2_ObjectKindNameLookup[Object1.Kind], S2_ObjectKindNameLookup[Object2.Kind]);
                    S2_StackPush(Runtime, S2_MakeNil());
                }
            } break;
            case S2_Op_Subtract:
            {
                scene2_object Object1 = S2_StackPop(Runtime);
                scene2_object Object2 = S2_StackPop(Runtime);
                if(Object1.Kind == S2_ObjectKind_Number && Object2.Kind == S2_ObjectKind_Number)
                {
                    S2_StackPush(Runtime, S2_MakeNumber(Object2.As.Number-Object1.As.Number));
                }
                else
                {
                    S2_PushMessageF(Arena, &Result.Messages, T_EmptyToken, "Trying to subtract two incompatible types: %S & %S. Will return nil.", S2_ObjectKindNameLookup[Object1.Kind], S2_ObjectKindNameLookup[Object2.Kind]);
                    S2_StackPush(Runtime, S2_MakeNil());
                }
            } break;
            case S2_Op_Multiply:
            {
                scene2_object Object1 = S2_StackPop(Runtime);
                scene2_object Object2 = S2_StackPop(Runtime);
                if(Object1.Kind == S2_ObjectKind_Number && Object2.Kind == S2_ObjectKind_Number)
                {
                    S2_StackPush(Runtime, S2_MakeNumber(Object2.As.Number*Object1.As.Number));
                }
                else
                {
                    S2_PushMessageF(Arena, &Result.Messages, T_EmptyToken, "Trying to multiply two incompatible types: %S & %S. Will return nil.", S2_ObjectKindNameLookup[Object1.Kind], S2_ObjectKindNameLookup[Object2.Kind]);
                    S2_StackPush(Runtime, S2_MakeNil());
                }
            } break;
            case S2_Op_Divide:
            {
                scene2_object Object1 = S2_StackPop(Runtime);
                scene2_object Object2 = S2_StackPop(Runtime);
                if(Object1.Kind == S2_ObjectKind_Number && Object2.Kind == S2_ObjectKind_Number)
                {
                    S2_StackPush(Runtime, S2_MakeNumber(Object2.As.Number/Object1.As.Number));
                }
                else
                {
                    S2_PushMessageF(Arena, &Result.Messages, T_EmptyToken, "Trying to divide two incompatible types: %S & %S. Will return nil.", S2_ObjectKindNameLookup[Object1.Kind], S2_ObjectKindNameLookup[Object2.Kind]);
                    S2_StackPush(Runtime, S2_MakeNil());
                }
            } break;
            case S2_Op_Equals:
            {
                scene2_object Object1 = S2_StackPop(Runtime);
                scene2_object Object2 = S2_StackPop(Runtime);
                if(Object1.Kind == S2_ObjectKind_Bool && Object2.Kind == S2_ObjectKind_Bool)
                {
                    S2_StackPush(Runtime, S2_MakeBool(Object1.As.Bool == Object2.As.Bool));
                }
                else if(Object1.Kind == S2_ObjectKind_Number && Object2.Kind == S2_ObjectKind_Number)
                {
                    S2_StackPush(Runtime, S2_MakeBool(Object1.As.Number == Object2.As.Number));
                }
                else if(Object1.Kind == S2_ObjectKind_String && Object2.Kind == S2_ObjectKind_Number)
                {
                    S2_StackPush(Runtime, S2_MakeBool(DoubleFromString(Object1.As.String) == Object1.As.Number));
                }
                else if(Object1.Kind == S2_ObjectKind_Number && Object2.Kind == S2_ObjectKind_String)
                {
                    S2_StackPush(Runtime, S2_MakeBool(Object1.As.Number == DoubleFromString(Object2.As.String)));
                }
                else
                {
                    S2_StackPush(Runtime, S2_MakeBool(false));
                }
            } break;
            case S2_Op_Greater:
            {
                scene2_object Object1 = S2_StackPop(Runtime);
                scene2_object Object2 = S2_StackPop(Runtime);
                if(Object1.Kind == S2_ObjectKind_Number && Object2.Kind == S2_ObjectKind_Number)
                {
                    S2_StackPush(Runtime, S2_MakeBool(Object2.As.Number > Object1.As.Number));
                }
                else
                {
                    S2_PushMessageF(Arena, &Result.Messages, T_EmptyToken, "Trying to compare two incompatible types: %S & %S. Will return nil.", S2_ObjectKindNameLookup[Object1.Kind], S2_ObjectKindNameLookup[Object2.Kind]);
                    S2_StackPush(Runtime, S2_MakeBool(false));
                }
            } break;
            case S2_Op_Less:
            {
                scene2_object Object1 = S2_StackPop(Runtime);
                scene2_object Object2 = S2_StackPop(Runtime);
                if(Object1.Kind == S2_ObjectKind_Number && Object2.Kind == S2_ObjectKind_Number)
                {
                    S2_StackPush(Runtime, S2_MakeBool(Object2.As.Number < Object1.As.Number));
                }
                else
                {
                    S2_PushMessageF(Arena, &Result.Messages, T_EmptyToken, "Trying to compare two incompatible types: %S & %S. Will return nil.", S2_ObjectKindNameLookup[Object1.Kind], S2_ObjectKindNameLookup[Object2.Kind]);
                    S2_StackPush(Runtime, S2_MakeBool(false));
                }
            } break;
            case S2_Op_Not:
            {
                scene2_object Object = S2_StackPop(Runtime);
                S2_StackPush(Runtime, S2_MakeBool(!S2_ObjectIsTrue(Object)));
            } break;
            case S2_Op_Negate:
            {
                scene2_object Object = S2_StackPop(Runtime);
                if(Object.Kind == S2_ObjectKind_Number)
                {
                    S2_StackPush(Runtime, S2_MakeBool(-Object.As.Number));
                }
                else
                {
                    S2_PushMessageF(Arena, &Result.Messages, T_EmptyToken, "Trying to negate invalid type: %S. Will return nil.", S2_ObjectKindNameLookup[Object.Kind]);
                    S2_StackPush(Runtime, S2_MakeNil());
                }
            } break;
        }
        Runtime->InstructionPointer += 1;
    }
    
    Done:;
    if(Runtime->InstructionPointer >= ProcInfo->InstructionEnd)
    {
        S2_SetCurrentProc(Runtime, S_EmptyProcInfo);
    }
    return(Result);
}