1 /** 2 * Copyright © Novelate 2020 3 * License: MIT (https://github.com/Novelate/NovelateEngine/blob/master/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 * Website: https://novelate.com/ 6 * ------ 7 * Novelate is a free and open-source visual novel engine and framework written in the D programming language. 8 * It can be used freely for both personal and commercial projects. 9 * ------ 10 * Module Description: 11 * This module handles parsing of the visual novel scripting files. 12 */ 13 module novelate.scripting.parser; 14 15 import novelate.scripting.config; 16 import novelate.scripting.scene; 17 import novelate.scripting.files; 18 import novelate.media; 19 import novelate.character; 20 import novelate.music; 21 22 /** 23 * Parses a Novelate file. 24 * Params: 25 * file = The file to parse. 26 */ 27 void parseFile(string file) 28 { 29 import std.path : absolutePath, dirName; 30 31 parse(cast(string)readFileText(file)); 32 } 33 34 private 35 { 36 /// The parsing types. 37 enum ParsingType 38 { 39 none, 40 config, 41 media, 42 character, 43 music, 44 scene 45 } 46 } 47 48 /** 49 * Parses content using the Novelate file format. 50 * Params: 51 * content = The content to parse. 52 */ 53 void parse(string content) 54 { 55 import std.array : array, replace, split; 56 import std..string : strip, stripLeft, stripRight, format; 57 import std.algorithm : map, filter, startsWith, endsWith, countUntil; 58 59 auto lines = content.replace("\r", "").split("\n").map!(l => l.strip).filter!(l => l.length).array; 60 61 ParsingType parsingType; 62 string currentName; 63 NovelateScene currentScene; 64 65 foreach (l; lines) 66 { 67 auto line = l.split("//")[0].stripRight; 68 69 if (line.startsWith("#")) 70 { 71 auto fileName = line[1 .. $]; 72 73 parse(readFileText(fileName ~ ".txt")); 74 } 75 76 if (line.startsWith("<") && line.endsWith(">")) 77 { 78 parsingType = ParsingType.none; 79 currentScene = null; 80 81 auto entryData = line[1 .. $-1]; 82 83 if (entryData == "__Config__") 84 { 85 parsingType = ParsingType.config; 86 } 87 else 88 { 89 auto entries = entryData.split(":"); 90 auto name = entries[0]; 91 auto type = entries[1]; 92 93 currentName = name; 94 95 switch (type) 96 { 97 case "Media": parsingType = ParsingType.media; break; 98 case "Character": 99 { 100 parsingType = ParsingType.character; 101 createCharacterBase(name); 102 break; 103 } 104 case "Music": parsingType = ParsingType.music; break; 105 case "Scene": 106 { 107 parsingType = ParsingType.scene; 108 currentScene = createSceneBase(name); 109 break; 110 } 111 default: parsingType = ParsingType.none; 112 } 113 } 114 } 115 else 116 { 117 switch (parsingType) 118 { 119 case ParsingType.config: 120 { 121 auto valueIndex = line.countUntil("="); 122 123 if (valueIndex < 1) 124 { 125 break; 126 } 127 128 auto name = line[0 .. valueIndex]; 129 auto value = line[valueIndex + 1 .. $]; 130 131 config.setConfig(name, value); 132 break; 133 } 134 135 case ParsingType.media: 136 { 137 addMediaFile(currentName, line); 138 break; 139 } 140 141 case ParsingType.character: 142 { 143 auto valueIndex = line.countUntil("="); 144 145 if (valueIndex < 1) 146 { 147 break; 148 } 149 150 auto name = line[0 .. valueIndex]; 151 auto value = line[valueIndex + 1 .. $]; 152 153 updateCharacter(currentName, name, value); 154 break; 155 } 156 157 case ParsingType.music: 158 { 159 addMusicFile(currentName, line); 160 break; 161 } 162 163 case ParsingType.scene: 164 { 165 if (currentScene) 166 { 167 currentScene.updateScene(line); 168 } 169 break; 170 } 171 172 default: break; 173 } 174 } 175 } 176 }