#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:; return(Result); }