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.parser;
14 
15 import novelate.config;
16 import novelate.media;
17 import novelate.character;
18 import novelate.music;
19 import novelate.scene;
20 import novelate.files;
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.stdio : writeln, writefln;
56 
57   import std.array : array, replace, split;
58   import std.string : strip, stripLeft, stripRight, format;
59   import std.algorithm : map, filter, startsWith, endsWith, countUntil;
60 
61   auto lines = content.replace("\r", "").split("\n").map!(l => l.strip).filter!(l => l.length).array;
62 
63   ParsingType parsingType;
64   string currentName;
65   NovelateScene currentScene;
66 
67   foreach (l; lines)
68   {
69     auto line = l.split("//")[0].stripRight;
70 
71     if (line.startsWith("#"))
72     {
73       auto fileName = line[1 .. $];
74 
75       parse(readFileText(fileName ~ ".txt"));
76     }
77 
78     if (line.startsWith("<") && line.endsWith(">"))
79     {
80       parsingType = ParsingType.none;
81       currentScene = null;
82 
83       auto entryData = line[1 .. $-1];
84 
85       if (entryData == "__Config__")
86       {
87         parsingType = ParsingType.config;
88       }
89       else
90       {
91         auto entries = entryData.split(":");
92         auto name = entries[0];
93         auto type = entries[1];
94 
95         currentName = name;
96 
97         switch (type)
98         {
99           case "Media": parsingType = ParsingType.media; break;
100           case "Character":
101           {
102             parsingType = ParsingType.character;
103             createCharacterBase(name);
104             break;
105           }
106           case "Music": parsingType = ParsingType.music; break;
107           case "Scene":
108           {
109             parsingType = ParsingType.scene;
110             currentScene = createSceneBase(name);
111             break;
112           }
113           default: parsingType = ParsingType.none;
114         }
115       }
116     }
117     else
118     {
119       switch (parsingType)
120       {
121         case ParsingType.config:
122         {
123           auto valueIndex = line.countUntil("=");
124 
125           if (valueIndex < 1)
126           {
127             break;
128           }
129 
130           auto name = line[0 .. valueIndex];
131           auto value = line[valueIndex + 1 .. $];
132 
133           config.setConfig(name, value);
134           break;
135         }
136 
137         case ParsingType.media:
138         {
139           addMediaFile(currentName, line);
140           break;
141         }
142 
143         case ParsingType.character:
144         {
145           auto valueIndex = line.countUntil("=");
146 
147           if (valueIndex < 1)
148           {
149             break;
150           }
151 
152           auto name = line[0 .. valueIndex];
153           auto value = line[valueIndex + 1 .. $];
154 
155           updateCharacter(currentName, name, value);
156           break;
157         }
158 
159         case ParsingType.music:
160         {
161           addMusicFile(currentName, line);
162           break;
163         }
164 
165         case ParsingType.scene:
166         {
167           if (currentScene)
168           {
169             currentScene.updateScene(line);
170           }
171           break;
172         }
173 
174         default: break;
175       }
176     }
177   }
178 }