#include "generated/vn_scene.meta.h" #include "generated/vn_scene.meta.c" //////////////////////////////// //~ sixten: Scene Compiler Functions static void S_ParseError(scene_compiler *Compiler, char *Message, s64 TokenOffset) { if(!Compiler->InPanicMode) { Compiler->EncounteredError = true; Compiler->InPanicMode = true; scene_compile_error *Error = PushStruct(Compiler->Arena, scene_compile_error); Error->Message = PushFormat(Compiler->Arena, Message); Error->Token = Compiler->At[TokenOffset]; QueuePush(Compiler->Errors.First, Compiler->Errors.Last, Error); Compiler->Errors.Count += 1; } } static void S_EmitByte(scene_compiler *Compiler, u8 Byte) { scene_emission_target *Target = &Compiler->TargetStack[Compiler->TargetStackIndex]; scene_bytecode_bucket *Bucket = Target->Bucket; scene_bytecode_chunk *Chunk = Bucket->Last; if(!Chunk || Chunk->Count >= ArrayCount(Chunk->Data) || (Target->Type == S_EmissionTarget_Named && !AreEqual(Chunk->Name, Target->Name))) { Chunk = PushStruct(Target->Arena, scene_bytecode_chunk); Chunk->Name = Target->Name; QueuePush(Bucket->First, Bucket->Last, Chunk); Bucket->Count += 1; } Chunk->Data[Chunk->Count] = Byte; Chunk->Count += 1; } static void S_EmitU32(scene_compiler *Compiler, u32 Value) { S_EmitByte(Compiler, Value >> 0); S_EmitByte(Compiler, Value >> 8); S_EmitByte(Compiler, Value >> 16); S_EmitByte(Compiler, Value >> 24); } static void S_EmitBucket(scene_compiler *Compiler, scene_bytecode_bucket *Bucket) { for(scene_bytecode_chunk *Chunk = Bucket->First; Chunk != 0; Chunk = Chunk->Next) { for(s64 Index = 0; Index < Chunk->Count; Index += 1) { S_EmitByte(Compiler, Chunk->Data[Index]); } } } static u64 S_MakeConstant(scene_compiler *Compiler, scene_value Value) { scene_value_chunk *Chunk = Compiler->LastValueChunk; if(!Chunk || Chunk->Count >= ArrayCount(Chunk->Values)) { Chunk = PushStruct(Compiler->Arena, scene_value_chunk); QueuePush(Compiler->FirstValueChunk, Compiler->LastValueChunk, Chunk); } u64 Result = Compiler->ValueCount; Chunk->Values[Chunk->Count] = Value; Chunk->Count += 1; Compiler->ValueCount += 1; return(Result); } static void S_EmitConstant(scene_compiler *Compiler, scene_value Value) { S_EmitByte(Compiler, S_Op_Constant); S_EmitU32(Compiler, S_MakeConstant(Compiler, Value)); } static void S_SetEmissionTarget(scene_compiler *Compiler, scene_emission_target Target) { Compiler->TargetStack[Compiler->TargetStackIndex] = Target; } static void S_PushEmissionTarget(scene_compiler *Compiler, scene_emission_target Target) { Compiler->TargetStackIndex += 1; Compiler->TargetStack[Compiler->TargetStackIndex] = Target; } static void S_PopEmissionTarget(scene_compiler *Compiler) { Compiler->TargetStackIndex -= 1; } static scene_bytecode_chunk *S_FindBytecodeChunkByName(scene_compiler *Compiler, string Name) { scene_bytecode_chunk *Result = 0; u64 Hash = HashString(Name); scene_bytecode_bucket *Bucket = &Compiler->ProcBuckets[Hash%ArrayCount(Compiler->ProcBuckets)]; for(scene_bytecode_chunk *Chunk = Bucket->First; Chunk != 0; Chunk = Chunk->Next) { if(AreEqual(Chunk->Name, Name)) { Result = Chunk; break; } } return(Result); } static void S_AdvanceCompiler(scene_compiler *Compiler) { Compiler->At += 1; } static scene_parse_rule S_ParseRuleFromToken(scene_compiler *Compiler, token Token) { scene_parse_rule Result = {}; switch(Token.Kind) { case TokenKind_ParenthesisOpen: { Result = { S_ParseGrouping, 0, S_Precedence_None }; } break; case TokenKind_Bang: { Result = { S_ParseUnary, 0, S_Precedence_None }; } break; case TokenKind_Minus: { Result = { S_ParseUnary, S_ParseBinary, S_Precedence_Term }; } break; case TokenKind_Plus: { Result = { 0, S_ParseBinary, S_Precedence_Term }; } break; case TokenKind_Star: { Result = { 0, S_ParseBinary, S_Precedence_Factor }; } break; case TokenKind_Slash: { Result = { 0, S_ParseBinary, S_Precedence_Factor }; } break; case TokenKind_EqualEqual: { Result = { 0, S_ParseBinary, S_Precedence_Equality }; } break; case TokenKind_BangEqual: { Result = { 0, S_ParseBinary, S_Precedence_Equality }; } break; case TokenKind_Greater: { Result = { 0, S_ParseBinary, S_Precedence_Comparison }; } break; case TokenKind_GreaterEqual: { Result = { 0, S_ParseBinary, S_Precedence_Comparison }; } break; case TokenKind_Less: { Result = { 0, S_ParseBinary, S_Precedence_Comparison }; } break; case TokenKind_LessEqual: { Result = { 0, S_ParseBinary, S_Precedence_Comparison }; } break; case TokenKind_False: { Result = { S_ParseLiteral, 0, S_Precedence_None }; } break; case TokenKind_True: { Result = { S_ParseLiteral, 0, S_Precedence_None }; } break; case TokenKind_Numeric: { Result = { S_ParseNumber, 0, S_Precedence_None }; } break; case TokenKind_StringLiteral: { Result = { S_ParseString, 0, S_Precedence_None }; } break; case TokenKind_Identifier: { Result = { S_ParseVariable, 0, S_Precedence_None }; } break; default: { //InvalidCodepath; } break; } return(Result); } static b32 S_MatchToken(scene_compiler *Compiler, token Token, token_kind Kind) { b32 Result = false; string String = T_StringFromToken(Compiler->Text, Token); if(Token.Kind == Kind) { Result = true; } return(Result); } static token S_ConsumeToken(scene_compiler *Compiler, token_kind Kind, char *Message) { token Token = Compiler->At[0]; string String = T_StringFromToken(Compiler->Text, Token); if(Token.Kind != Kind) { S_ParseError(Compiler, Message); } Compiler->At += 1; return(Token); } static void S_ParseTopLevelDeclaration(scene_compiler *Compiler) { if(Compiler->At[0].Kind == TokenKind_Proc) { Compiler->At += 1; S_ParseProcedure(Compiler); } else if(Compiler->At[0].Kind == TokenKind_Var) { Compiler->At += 1; S_ParseVariableDeclaration(Compiler); } else if(Compiler->At[0].Kind == TokenKind_Nav) { Compiler->At += 1; S_ParseNavFilePath(Compiler); } else if(Compiler->At[0].Kind == TokenKind_Background) { Compiler->At += 1; S_ParseBackgroundAsset(Compiler); } else { S_ParseError(Compiler, "Expected top-level declaration (proc, var, background or nav).", 0); Compiler->At += 1; } } static void S_ParseNavFilePath(scene_compiler *Compiler) { token NameToken = Compiler->At[0]; Compiler->At += 1; if(NameToken.Kind == TokenKind_StringLiteral) { Compiler->NavFileName = Substring(Compiler->Text, Pad(NameToken.Range, -1)); if(Compiler->At[0].Kind == TokenKind_Semicolon) { // sixten: all good Compiler->At += 1; } else { S_ParseError(Compiler, "Expected ';'.", 0); } } else { S_ParseError(Compiler, "Expected nav file name."); } } static void S_ParseBackgroundAsset(scene_compiler *Compiler) { token AssetNameToken = Compiler->At[0]; Compiler->At += 1; if(AssetNameToken.Kind == TokenKind_Identifier) { //- sixten: find asset id by name string AssetName = Substring(Compiler->Text, AssetNameToken.Range); Compiler->BackgroundTexture = AssetID_None; for(u64 AssetIndex = 0; AssetIndex < AssetID_COUNT; AssetIndex += 1) { if(AreEqual(MakeString(AssetNameLUT[AssetIndex]), AssetName)) { Compiler->BackgroundTexture = AssetIndex; goto ValidAssetFound; } } S_ParseError(Compiler, "Invalid asset name."); ValidAssetFound:; if(Compiler->At[0].Kind == TokenKind_Semicolon) { // sixten: all good Compiler->At += 1; } else { S_ParseError(Compiler, "Expected ';'.", 0); } } } static void S_ParseProcedure(scene_compiler *Compiler) { token NameToken = S_ConsumeToken(Compiler, TokenKind_Identifier, "Expected procedure name after 'proc'"); S_ConsumeToken(Compiler, TokenKind_CurlyOpen, "Expected '{' after procedure name."); if(!Compiler->InPanicMode) { string Name = T_StringFromToken(Compiler->Text, NameToken); scene_bytecode_bucket *Bucket = &Compiler->ProcBuckets[HashString(Name)%ArrayCount(Compiler->ProcBuckets)]; S_SetEmissionTarget(Compiler, S_NamedEmissionTarget(Compiler->Arena, Bucket, Name)); for(;Compiler->At < Compiler->TokensEnd;) { if(Compiler->At[0].Kind == TokenKind_CurlyClose) { Compiler->At += 1; break; } else { S_ParseDeclaration(Compiler); } } } } static void S_ParseDeclaration(scene_compiler *Compiler) { switch(Compiler->At[0].Kind) { case TokenKind_Var: { Compiler->At += 1; S_ParseVariableDeclaration(Compiler); } break; case TokenKind_Jump: { Compiler->At += 1; S_ParseJumpStatement(Compiler); } break; case TokenKind_Branch: { Compiler->At += 1; S_ParseBranchStatement(Compiler); } break; case TokenKind_At: { Compiler->At += 1; token IdentifierToken = S_ConsumeToken(Compiler, TokenKind_Identifier, "Expected identifier after '@'"); S_EmitConstant(Compiler, S_MakeSourceRef(IdentifierToken)); if(Compiler->At->Kind == TokenKind_ParenthesisOpen) { Compiler->At += 1; token StateToken = S_ConsumeToken(Compiler, TokenKind_Identifier, "Expected character state."); S_ConsumeToken(Compiler, TokenKind_ParenthesisClose, "Expected ')' after character state."); S_EmitConstant(Compiler, S_MakeSourceRef(StateToken)); } else { S_EmitConstant(Compiler, S_MakeNil()); } S_EmitByte(Compiler, S_Op_ShowCharacter); if(Compiler->At->Kind == TokenKind_Semicolon) { Compiler->At += 1; } } break; case TokenKind_StringLiteral: { S_ParseExpression(Compiler); //- sixten: parse tags { b32 EmitAwait = true; b32 EmitClear = true; for(;Compiler->At[0].Kind == TokenKind_PoundSign;) { Compiler->At += 1; token TagToken = S_ConsumeToken(Compiler, TokenKind_Identifier, "Expected tag name after '#'."); string TagString = T_StringFromToken(Compiler->Text, TagToken); if(AreEqual(TagString, StrLit("noclear"))) { EmitClear = false; } else if(AreEqual(TagString, StrLit("noawait"))) { EmitAwait = false; } else { S_ParseError(Compiler, "Unknown tag."); } } if(EmitClear) { S_EmitByte(Compiler, S_Op_ClearDialog); } S_EmitByte(Compiler, S_Op_LineEntry); if(EmitAwait) { S_EmitByte(Compiler, S_Op_AwaitInput); } S_ConsumeToken(Compiler, TokenKind_Semicolon, "Expected ';' after statement."); } } break; default: { S_ParseStatement(Compiler); S_EmitByte(Compiler, S_Op_Pop); } break; } if(Compiler->InPanicMode) { for(;Compiler->At < Compiler->TokensEnd;) { if(Compiler->At[-1].Kind == TokenKind_Semicolon) { goto End; } switch(Compiler->At[0].Kind) { case TokenKind_Var: goto End; case TokenKind_StringLiteral: goto End; case TokenKind_Jump: goto End; case TokenKind_Branch: goto End; default: break; } Compiler->At += 1; } End: Compiler->InPanicMode = false; } } static void S_ParseVariableDeclaration(scene_compiler *Compiler) { S_ConsumeToken(Compiler, TokenKind_Identifier, "Expected variable name."); u64 NameConstant = S_MakeConstant(Compiler, S_MakeSourceRef(Compiler->At[-1])); if(Compiler->At[0].Kind == TokenKind_Equal) { Compiler->At += 1; S_ParseExpression(Compiler); } else { S_EmitByte(Compiler, S_Op_Nil); } S_ConsumeToken(Compiler, TokenKind_Semicolon, "Expected ';' after variable declaration."); S_EmitByte(Compiler, S_Op_DefineGlobal); Assert(NameConstant < U32_Max); S_EmitU32(Compiler, NameConstant); } static void S_ParseVariable(scene_compiler *Compiler, b32 CanAssign) { S_ParseNamedVariable(Compiler, Compiler->At[-1], CanAssign); } static void S_ParseNamedVariable(scene_compiler *Compiler, token Token, b32 CanAssign) { u64 NameConstant = S_MakeConstant(Compiler, S_MakeSourceRef(Token)); if(CanAssign && Compiler->At[0].Kind == TokenKind_Equal) { Compiler->At += 1; S_ParseExpression(Compiler); S_EmitByte(Compiler, S_Op_SetGlobal); } else { S_EmitByte(Compiler, S_Op_GetGlobal); } S_EmitU32(Compiler, NameConstant); } static void S_ParseJumpStatement(scene_compiler *Compiler) { S_ConsumeToken(Compiler, TokenKind_Identifier, "Expected jump destination."); token DestToken = Compiler->At[-1]; S_EmitByte(Compiler, S_Op_Jump); S_EmitU32(Compiler, S_MakeConstant(Compiler, S_MakeSourceRef(DestToken))); S_ConsumeToken(Compiler, TokenKind_Semicolon, "Expected ';' after jump statement."); } static void S_ParseBranchStatement(scene_compiler *Compiler) { temp Scratch = GetScratch(); scene_branch_case *FirstBranch = 0, *LastBranch = 0; S_ConsumeToken(Compiler, TokenKind_CurlyOpen, "Expected '{' after 'branch'."); for(;Compiler->At < Compiler->TokensEnd;) { if(Compiler->At[0].Kind == TokenKind_CurlyClose) { Compiler->At += 1; break; } else { //- sixten: parse branch declarations switch(Compiler->At[0].Kind) { case TokenKind_StringLiteral: { scene_branch_case *Branch = S_ParseBranchCase(Compiler, Scratch.Arena); QueuePush(FirstBranch, LastBranch, Branch); } break; default: { S_ParseError(Compiler, "Expected branch case."); Compiler->At += 1; } break; } } } //- sixten: emit add_branch calls for(scene_branch_case *Branch = FirstBranch; Branch != 0; Branch = Branch->Next) { S_EmitByte(Compiler, S_Op_AddBranch); S_EmitU32(Compiler, S_MakeConstant(Compiler, S_MakeSourceRef(Branch->Name))); S_EmitU32(Compiler, S_MakeConstant(Compiler, S_MakeOffset(0))); scene_value_chunk *Chunk = Compiler->LastValueChunk; Branch->OffsetValue = &Chunk->Values[Chunk->Count-1]; } S_EmitByte(Compiler, S_Op_Halt); // sixten: We need to keep track of the amount of bytes that have been emitted between branch cases, such that we can patch our relative jumps // to point to the correct address. s64 BaseOffset = 0; //- sixten: emit branch contents for(scene_branch_case *Branch = FirstBranch; Branch != 0; Branch = Branch->Next) { //- sixten: patch branch jump dest Branch->OffsetValue->Offset = BaseOffset; if(Branch->Bucket.Count > 0) { S_EmitBucket(Compiler, &Branch->Bucket); BaseOffset += (Branch->Bucket.Count - 1)*ArrayCount(scene_bytecode_chunk::Data)+Branch->Bucket.Last->Count; } S_EmitByte(Compiler, S_Op_JumpClose); BaseOffset += 1; S_EmitU32(Compiler, S_MakeConstant(Compiler, S_MakeOffset(BaseOffset))); BaseOffset += 4; scene_value_chunk *Chunk = Compiler->LastValueChunk; Branch->EndOffsetValue = &Chunk->Values[Chunk->Count-1]; } //- sixten: patch the last jump for(scene_branch_case *Branch = FirstBranch; Branch != 0; Branch = Branch->Next) { // sixten(NOTE): This little line here feels rather sketchy, it may one day fail on us. Branch->EndOffsetValue->Offset = BaseOffset-Branch->EndOffsetValue->Offset-4; } ReleaseScratch(Scratch); } static scene_branch_case *S_ParseBranchCase(scene_compiler *Compiler, arena *Arena) { scene_branch_case *Branch = PushStruct(Arena, scene_branch_case); Branch->Name = S_ConsumeToken(Compiler, TokenKind_StringLiteral, "Expected branch label at start of branch case."); S_ConsumeToken(Compiler, TokenKind_CurlyOpen, "Expected '{' after branch label."); S_PushEmissionTarget(Compiler, S_RawEmissionTarget(Arena, &Branch->Bucket)); for(;Compiler->At < Compiler->TokensEnd;) { if(Compiler->At[0].Kind == TokenKind_CurlyClose) { Compiler->At += 1; break; } else { S_ParseDeclaration(Compiler); } } S_PopEmissionTarget(Compiler); return(Branch); } static void S_ParseStatement(scene_compiler *Compiler) { S_ParseExpression(Compiler); S_ConsumeToken(Compiler, TokenKind_Semicolon, "Expected ';' after statement."); } static void S_ParseExpression(scene_compiler *Compiler) { S_ParsePrecedence(Compiler, S_Precedence_Assignment); } static void S_ParseLiteral(scene_compiler *Compiler, b32 CanAssign) { string Value = T_StringFromToken(Compiler->Text, Compiler->At[-1]); switch(Compiler->At[-1].Kind) { case TokenKind_False: { S_EmitByte(Compiler, S_Op_False); } break; case TokenKind_True: { S_EmitByte(Compiler, S_Op_True); } break; InvalidDefaultCase; } } static void S_ParseNumber(scene_compiler *Compiler, b32 CanAssign) { r64 Value = DoubleFromString(T_StringFromToken(Compiler->Text, Compiler->At[-1])); S_EmitConstant(Compiler, S_MakeNumber(Value)); } static void S_ParseString(scene_compiler *Compiler, b32 CanAssign) { S_EmitConstant(Compiler, S_MakeSourceRef(Pad(Compiler->At[-1].Range, -1))); } static void S_ParseGrouping(scene_compiler *Compiler, b32 CanAssign) { S_ParseExpression(Compiler); S_ConsumeToken(Compiler, TokenKind_ParenthesisClose, "Expected ')' after expression."); } static void S_ParseUnary(scene_compiler *Compiler, b32 CanAssign) { scene_operator Operator = S_OperatorFromString(T_StringFromToken(Compiler->Text, Compiler->At[-1])); S_ParsePrecedence(Compiler, S_Precedence_Unary); switch(Operator) { case S_Operator_Minus: { S_EmitByte(Compiler, S_Op_Negate); } break; case S_Operator_Not: { S_EmitByte(Compiler, S_Op_Not); } break; InvalidDefaultCase; } } static void S_ParseBinary(scene_compiler *Compiler, b32 CanAssign) { token Token = Compiler->At[-1]; scene_operator Operator = S_OperatorFromString(T_StringFromToken(Compiler->Text, Token)); scene_parse_rule Rule = S_ParseRuleFromToken(Compiler, Token); S_ParsePrecedence(Compiler, (scene_precedence)(Rule.Precedence + 1)); switch(Operator) { case S_Operator_Add: { S_EmitByte(Compiler, S_Op_Add); } break; case S_Operator_Minus: { S_EmitByte(Compiler, S_Op_Subtract); } break; case S_Operator_Multiply: { S_EmitByte(Compiler, S_Op_Multiply); } break; case S_Operator_Divide: { S_EmitByte(Compiler, S_Op_Divide); } break; case S_Operator_Equals: { S_EmitByte(Compiler, S_Op_Equal); } break; case S_Operator_NotEquals: { S_EmitByte(Compiler, S_Op_Equal); S_EmitByte(Compiler, S_Op_Not); } break; case S_Operator_Greater: { S_EmitByte(Compiler, S_Op_Greater); } break; case S_Operator_GreaterThanOrEquals: { S_EmitByte(Compiler, S_Op_Less); S_EmitByte(Compiler, S_Op_Not); } break; case S_Operator_Less: { S_EmitByte(Compiler, S_Op_Less); } break; case S_Operator_LessThanOrEquals: { S_EmitByte(Compiler, S_Op_Greater); S_EmitByte(Compiler, S_Op_Not); } break; InvalidDefaultCase; } } static void S_ParsePrecedence(scene_compiler *Compiler, scene_precedence Precedence) { b32 CanAssign = (Precedence <= S_Precedence_Assignment); S_AdvanceCompiler(Compiler); scene_parse_rule Rule = S_ParseRuleFromToken(Compiler, Compiler->At[-1]); if(Rule.PrefixRule) { Rule.PrefixRule(Compiler, CanAssign); } else { S_ParseError(Compiler, "Expected expression."); } while(Precedence <= (Rule = S_ParseRuleFromToken(Compiler, Compiler->At[0])).Precedence) { S_AdvanceCompiler(Compiler); Rule.InfixRule(Compiler, CanAssign); } if(CanAssign && Compiler->At[0].Kind == TokenKind_Equal) { S_ParseError(Compiler, "Invalid assignment target."); } } struct proc_from_chunks_result { scene_proc *Proc; scene_bytecode_chunk *NextChunk; }; static proc_from_chunks_result S_ProcFromChunks(arena *Arena, scene_bytecode_chunk *First) { Assert(First != 0); string ChunkName = First->Name; //- sixten: find required bytes s64 RequiredBytes = 0; scene_bytecode_chunk *NextChunk = 0; { scene_bytecode_chunk *Chunk = First; for(;Chunk != 0 && AreEqual(Chunk->Name, ChunkName); Chunk = Chunk->Next) { RequiredBytes += Chunk->Count; } NextChunk = Chunk; } scene_proc *Proc = PushStruct(Arena, scene_proc); Proc->Name = ChunkName; Proc->Data = PushArray(Arena, u8, RequiredBytes); Proc->Count = RequiredBytes; //- sixten: copy over data from chunks u8 *Dest = Proc->Data; for(scene_bytecode_chunk *Chunk = First; Chunk != NextChunk; Chunk = Chunk->Next) { Copy(Dest, Chunk->Data, Chunk->Count); Dest += Chunk->Count; } //- sixten: fill & return proc_from_chunks_result Result; { Result.Proc = Proc; Result.NextChunk = NextChunk; } return(Result); } static compiled_scene S_ScriptFromText(arena *Arena, string Text) { compiled_scene Result = {}; temp Scratch = GetScratch(&Arena, 1); tokenize_result TokenizeResult = T_TokenizeFromText(Arena, Text, T_IsIrregular); scene_compiler Compiler = {}; { Compiler.Arena = Scratch.Arena; Compiler.Text = Text; Compiler.TokensBegin = TokenizeResult.Tokens.Tokens; Compiler.TokensEnd = Compiler.TokensBegin+TokenizeResult.Tokens.Count; Compiler.At = Compiler.TokensBegin; }; S_SetEmissionTarget(&Compiler, S_RawEmissionTarget(Compiler.Arena, &Compiler.GlobalScope)); //- sixten: append tokenization errors b32 FoundTokenizationError = false; for(tokenizer_message *Message = TokenizeResult.Messages.First; Message != 0; Message = Message->Next) { if(Message->Kind == T_MessageKind_Error) { S_ParseError(&Compiler, (char *)Message->String.Data); FoundTokenizationError = true; } } //- sixten: tokens -> bytecode if(!FoundTokenizationError) { for(;Compiler.At < Compiler.TokensEnd;) { S_ParseTopLevelDeclaration(&Compiler); } } //- sixten: bake global scope if(Compiler.GlobalScope.First) { proc_from_chunks_result ProcResult = S_ProcFromChunks(Arena, Compiler.GlobalScope.First); Result.GlobalScope = ProcResult.Proc; } //- sixten: bake compiled chunks for(s64 BucketIndex = 0; BucketIndex < ArrayCount(Compiler.ProcBuckets); BucketIndex += 1) { scene_bytecode_bucket *Bucket = &Compiler.ProcBuckets[BucketIndex]; for(scene_bytecode_chunk *Chunk = Bucket->First; Chunk != 0;) { proc_from_chunks_result ProcResult = S_ProcFromChunks(Arena, Chunk); s64 Hash = HashString(Chunk->Name); scene_proc_bucket *DestBucket = &Result.Buckets[Hash%ArrayCount(Result.Buckets)]; QueuePush(DestBucket->First, DestBucket->Last, ProcResult.Proc); Chunk = ProcResult.NextChunk; } } //- sixten: bake value chunks { Result.Values = PushArray(Arena, scene_value, Compiler.ValueCount); Result.ValueCount = Compiler.ValueCount; scene_value *Dest = Result.Values; for(scene_value_chunk *Chunk = Compiler.FirstValueChunk; Chunk != 0; Chunk = Chunk->Next) { Copy(Dest, Chunk->Values, Chunk->Count*sizeof(scene_value)); Dest += Chunk->Count; } } //- sixten: copy errors for(scene_compile_error *Error = Compiler.Errors.First; Error != 0; Error = Error->Next) { scene_compile_error *New = PushStruct(Arena, scene_compile_error); New->Message = PushString(Arena, Error->Message); New->Token = Error->Token; QueuePush(Result.Errors.First, Result.Errors.Last, New); } Result.Errors.Count = Compiler.Errors.Count; //- sixten: copy nav file name Result.NavFileName = Compiler.NavFileName; //- sixten: copy background texture handle Result.BackgroundTexture = Compiler.BackgroundTexture; // sixten(IMPORTANT): The text is assumed to remain in memory for the duration of the scene. Result.Source = Text; Result.IsValid = !Compiler.EncounteredError; ReleaseScratch(Scratch); return(Result); } static compiled_scene S_CopyCompiledScene(arena *Arena, compiled_scene *Compiled) { compiled_scene Result = {}; //- sixten(TODO): copy over the errors //Assert(Compiled->Errors.Count == 0); //- sixten: copy the global scope if(Compiled->GlobalScope) { Result.GlobalScope = PushStruct(Arena, scene_proc); Result.GlobalScope->Name = PushString(Arena, Compiled->GlobalScope->Name); Result.GlobalScope->Data = PushArray(Arena, u8, Compiled->GlobalScope->Count); Copy(Result.GlobalScope->Data, Compiled->GlobalScope->Data, Compiled->GlobalScope->Count); Result.GlobalScope->Count = Compiled->GlobalScope->Count; } //- sixten: copy the procs for(s64 BucketIndex = 0; BucketIndex < ArrayCount(Compiled->Buckets); BucketIndex += 1) { scene_proc_bucket *SourceBucket = &Compiled->Buckets[BucketIndex]; scene_proc_bucket *DestBucket = &Result.Buckets[BucketIndex]; for(scene_proc *Proc = SourceBucket->First; Proc != 0; Proc = Proc->Next) { scene_proc *NewProc = PushStruct(Arena, scene_proc); NewProc->Name = PushString(Arena, Proc->Name); NewProc->Data = PushArray(Arena, u8, Proc->Count); Copy(NewProc->Data, Proc->Data, Proc->Count); NewProc->Count = Proc->Count; QueuePush(DestBucket->First, DestBucket->Last, NewProc); } } //- sixten: copy the values Result.Values = PushArray(Arena, scene_value, Compiled->ValueCount); Copy(Result.Values, Compiled->Values, Compiled->ValueCount*sizeof(scene_value)); Result.ValueCount = Compiled->ValueCount; //- sixten: copy the source Result.Source = PushString(Arena, Compiled->Source); //- sixten: copy nav file Result.NavFileName = PushString(Arena, Compiled->NavFileName); //- sixten: copy background texture handle Result.BackgroundTexture = Compiled->BackgroundTexture; Result.IsValid = true;//Compiled->IsValid; // sixten(TODO): I don't know why this is commented out. return(Result); } //////////////////////////////// //~ sixten: Scene Runtime Functions static void S_RuntimeError(scene_runtime *Runtime, string Message) { scene_runtime_error *Error = PushStruct(Runtime->ErrorArena, scene_runtime_error); Error->Message = PushString(Runtime->ErrorArena, Message); DLLInsertLast(Runtime->Errors.First, Runtime->Errors.Last, Error); Runtime->Errors.Count += 1; } static void S_RuntimeErrorF(scene_runtime *Runtime, char *Format, ...) { va_list Arguments; va_start(Arguments, Format); scene_runtime_error *Error = PushStruct(Runtime->ErrorArena, scene_runtime_error); Error->Message = PushFormatVariadic(Runtime->ErrorArena, Format, Arguments); DLLInsertLast(Runtime->Errors.First, Runtime->Errors.Last, Error); Runtime->Errors.Count += 1; va_end(Arguments); } static scene_named_value *S_FindGlobalVariableByName(scene_runtime *Runtime, string Name, b32 AlwaysCreate) { scene_named_value *Result = 0; u64 Hash = HashString(Name); scene_named_value_bucket *Bucket = &Runtime->GlobalVariableBuckets[Hash%ArrayCount(Runtime->GlobalVariableBuckets)]; //- sixten: search for the value in the bucket for(scene_named_value_node *Node = Bucket->First; Node != 0; Node = Node->Next) { scene_named_value *Value = &Node->NamedValue; if(AreEqual(Name, Value->Name)) { Result = Value; break; } } //- sixten: create it if it doesn't exist if(Result == 0) { //- sixten: check the free list first scene_named_value_bucket *FreeList = &Runtime->GlobalVariableFreeList; scene_named_value_node *Node = FreeList->First; if(Node) { DLLRemove(FreeList->First, FreeList->Last, Node); } else { Node = PushStruct(Runtime->RuntimeArena, scene_named_value_node); } DLLInsertLast(Bucket->First, Bucket->Last, Node); Result = &Node->NamedValue; Result->Name = Name; } return(Result); } static scene_proc *S_FindProcByName(compiled_scene *Compiled, string Name) { scene_proc *Result = 0; u64 Hash = HashString(Name); scene_proc_bucket *Bucket = &Compiled->Buckets[Hash%ArrayCount(Compiled->Buckets)]; for(scene_proc *Proc = Bucket->First; Proc != 0; Proc = Proc->Next) { if(AreEqual(Proc->Name, Name)) { Result = Proc; break; } } return(Result); } static void S_PushStack(scene_runtime *Runtime, scene_value Value) { scene_runtime_stack *Stack = &Runtime->Stack; if(Stack->Count < ArrayCount(Stack->Stack)) { Stack->Stack[Stack->Count] = Value; Stack->Count += 1; } else { S_RuntimeError(Runtime, StrLit("Stack overflow")); } } static scene_value S_PopStack(scene_runtime *Runtime) { scene_value Result = {}; scene_runtime_stack *Stack = &Runtime->Stack; if(Stack->Count > 0) { Stack->Count -= 1; Result = Stack->Stack[Stack->Count]; } else { S_RuntimeError(Runtime, StrLit("Stack underflow")); } return(Result); } static scene_value S_PopStackAndImplicitConvert(scene_runtime *Runtime) { scene_value Result = S_PopStack(Runtime); if(Result.Kind == S_ValueKind_SourceRef) { Result.Kind = S_ValueKind_String; Result.String = Substring(Runtime->Compiled.Source, Result.SourceRef); } return(Result); } static void S_SetCurrentProc(scene_runtime *Runtime, scene_proc *Proc) { Runtime->CurrentProc = Proc; Runtime->IP = 0; Runtime->BranchCount = 0; } static void S_ResetRuntime(scene_runtime *Runtime) { S_SetCurrentProc(Runtime, 0); Fill(Runtime->GlobalVariableBuckets, 0, sizeof(Runtime->GlobalVariableBuckets)); ZeroStruct(&Runtime->GlobalVariableFreeList); ZeroStruct(&Runtime->Errors); if(Runtime->RuntimeArena) { ArenaClear(Runtime->RuntimeArena); } else { Runtime->RuntimeArena = ArenaAlloc(Kilobytes(16)); } } static scene_runtime_result S_Run(scene_runtime *Runtime, arena *FrameArena, b32 AdvanceOnAwait) { scene_runtime_result Result = {}; compiled_scene *Compiled = &Runtime->Compiled; Assert(Runtime != 0); Assert(Compiled->IsValid); // sixten(NOTE): This will only work on little endian architectures. #define S_ReadU32() (Runtime->IP += 4, *(u32 *)(Data+Runtime->IP-4)) #define S_ReadValue() Compiled->Values[S_ReadU32()] //- sixten: run the bytecode interpreter if(Runtime->CurrentProc) { if(Runtime->IP < Runtime->CurrentProc->Count) { u8 *Data = Runtime->CurrentProc->Data; switch(Data[Runtime->IP]) { case S_Op_Constant: { Runtime->IP += 1; S_PushStack(Runtime, S_ReadValue()); } break; case S_Op_Pop: { Runtime->IP += 1; S_PopStack(Runtime); } break; case S_Op_Nil: { Runtime->IP += 1; S_PushStack(Runtime, S_MakeNil()); } break; case S_Op_True: { Runtime->IP += 1; S_PushStack(Runtime, S_MakeBoolean(true)); } break; case S_Op_False: { Runtime->IP += 1; S_PushStack(Runtime, S_MakeBoolean(false)); } break; case S_Op_Add: { Runtime->IP += 1; scene_value Value1 = S_PopStackAndImplicitConvert(Runtime); scene_value Value2 = S_PopStackAndImplicitConvert(Runtime); if(Value1.Kind == S_ValueKind_Number && Value2.Kind == S_ValueKind_Number) { S_PushStack(Runtime, S_MakeNumber(Value1.Number + Value2.Number)); } else if(Value1.Kind == S_ValueKind_String && Value2.Kind == S_ValueKind_String) { S_RuntimeError(Runtime, StrLit("String concatination is not yet supported.")); } else { S_RuntimeError(Runtime, StrLit("This operation is not supported.")); } } break; case S_Op_Subtract: { Runtime->IP += 1; scene_value Value1 = S_PopStack(Runtime); scene_value Value2 = S_PopStack(Runtime); if(Value1.Kind == S_ValueKind_Number && Value2.Kind == S_ValueKind_Number) { S_PushStack(Runtime, S_MakeNumber(Value1.Number - Value2.Number)); } else { S_RuntimeError(Runtime, StrLit("This operation is not supported.")); } } break; case S_Op_Multiply: case S_Op_Divide: { S_RuntimeError(Runtime, StrLit("This operation is not supported.")); } break; case S_Op_DefineGlobal: { Runtime->IP += 1; scene_value Value = Runtime->Compiled.Values[S_ReadU32()]; if(Value.Kind == S_ValueKind_SourceRef) { string Name = Substring(Runtime->Compiled.Source, Value.SourceRef); scene_named_value *NamedValue = S_FindGlobalVariableByName(Runtime, Name); if(!NamedValue->Initialized) { NamedValue->Initialized = true; scene_value TargetValue = S_PopStackAndImplicitConvert(Runtime); NamedValue->Value = TargetValue; } else { S_RuntimeErrorF(Runtime, "Global %S is already defined.", Name); } } else { S_RuntimeError(Runtime, StrLit("Incorrect value kind when retrieving global name.")); } } break; case S_Op_Jump: { Runtime->IP += 1; scene_value Value = S_ReadValue(); if(Value.Kind == S_ValueKind_SourceRef) { string JumpDest = Substring(Compiled->Source, Value.SourceRef); scene_proc *Dest = S_FindProcByName(Compiled, JumpDest); if(Dest) { S_SetCurrentProc(Runtime, Dest); } else { S_RuntimeErrorF(Runtime, "Trying to jump to unknown proc: %S", JumpDest); } } else { S_RuntimeErrorF(Runtime, "Incorrect value kind when retrieving jump destination."); } } break; case S_Op_JumpClose: { Runtime->IP += 1; scene_value OffsetValue = S_ReadValue(); if(OffsetValue.Kind == S_ValueKind_Offset) { Runtime->IP += OffsetValue.Offset; } else { S_RuntimeErrorF(Runtime, "Incorrect value kind when retrieving offset."); } } break; case S_Op_AddBranch: { Runtime->IP += 1; scene_value BranchName = S_ReadValue(); if(BranchName.Kind == S_ValueKind_SourceRef) { scene_value Offset = S_ReadValue(); if(Offset.Kind == S_ValueKind_Offset) { branch_case *Branch = &Runtime->Branches[Runtime->BranchCount]; Branch->Name = Substring(Compiled->Source, Pad(BranchName.SourceRef, -1)); Branch->Offset = Offset.Offset; Runtime->BranchCount += 1; } else { S_RuntimeErrorF(Runtime, "Incorrect value kind when retrieving offset."); } } else { S_RuntimeErrorF(Runtime, "Incorrect value kind when retrieving branch name."); } } break; case S_Op_Halt: { Result.ReachedAwait = true; } break; case S_Op_AwaitInput: { if(AdvanceOnAwait) { Runtime->IP += 1; } Result.ReachedAwait = true; } break; case S_Op_ClearDialog: { scene_textbox_action *Action = PushStructNoClear(FrameArena, scene_textbox_action); Action->Kind = S_TextboxActionKind_Set; Action->String = StrLit(""); QueuePush(Runtime->FirstTextboxAction, Runtime->LastTextboxAction, Action); Runtime->IP += 1; } break; case S_Op_ShowCharacter: { Runtime->IP += 1; scene_value State = S_PopStackAndImplicitConvert(Runtime); scene_value CharacterValue = S_PopStackAndImplicitConvert(Runtime); string Character = StrLit(""); if(CharacterValue.Kind == S_ValueKind_String) { Character = CharacterValue.String; } else { S_RuntimeErrorF(Runtime, "Incorrect value kind when retrieving character name."); } // sixten: create & apply character action scene_character_action *Action = PushStruct(FrameArena, scene_character_action); Action->Target = Character; if(State.Kind == S_ValueKind_Nil) { Action->State = CR_State_Invalid; } else { Action->State = CR_CharacterStateFromString(State.String); } DLLInsertLast(Runtime->FirstCharacterAction, Runtime->LastCharacterAction, Action); } break; case S_Op_LineEntry: { scene_textbox_action *Action = PushStructNoClear(FrameArena, scene_textbox_action); Action->Kind = S_TextboxActionKind_Append; Runtime->IP += 1; scene_value Value = S_PopStack(Runtime); if(Value.Kind == S_ValueKind_SourceRef) { Action->String = Substring(Compiled->Source, Value.SourceRef); QueuePush(Runtime->FirstTextboxAction, Runtime->LastTextboxAction, Action); } else { S_RuntimeErrorF(Runtime, "Incorrect value kind when retrieving line entry."); } } break; default: { S_RuntimeErrorF(Runtime, "Unknown bytecode op: 0x%02x", Data[Runtime->IP]); } break; } } else { Result.ReachedAwait = true; Runtime->CurrentProc = 0; } } else { S_RuntimeErrorF(Runtime, "No main entry was found"); } Result.HadError = !DLLIsEmpty(Runtime->Errors.First); Runtime->LastResult = Result; return(Result); }