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 }