ScriptStack 1.0.4
Loading...
Searching...
No Matches
Script.cs
Go to the documentation of this file.
4using System;
5using System.Collections.Generic;
6using System.Collections.ObjectModel;
7using System.Globalization;
8using System.Text;
9using System.Text.Json;
10
11namespace ScriptStack.Runtime
12{
13
17 public class Script
18 {
19
20 #region Private Variables
21
22 private Manager manager;
23 private String scriptName;
24 private List<String> sourceCode;
25 private Executable executable;
26
27 #endregion
28
29 #region Private Methods
30
31 private void Scan(string scriptName)
32 {
33
34 Scanner scanner = manager.Scanner;
35 sourceCode = scanner.Scan(scriptName);
36 sourceCode.Add(" ");
37
38 Dictionary<string, bool> included = new Dictionary<string, bool>();
39
40 for (int i = 0; i < sourceCode.Count; i++)
41 {
42
43 string line = sourceCode[i];
44
45 Lexer lexer = new Lexer(new List<string> { line });
46
47 List<Token> tokenStream = null;
48
49 try
50 {
51 tokenStream = lexer.GetTokens();
52 }
53 catch (Exception)
54 {
55 continue;
56 }
57
58 if (tokenStream.Count == 0)
59 continue;
60
61 if (tokenStream[0].Type != TokenType.Include)
62 continue;
63
64 if (tokenStream.Count < 2)
65 throw new ParserException("Include Statement ohne Pfadangabe.");
66
67 if (tokenStream[1].Type != TokenType.String)
68 throw new ParserException("Nach einem 'include' Befehl wird ein String (Pfad) erwartet.");
69
70 if (tokenStream.Count < 3)
71 throw new ParserException("Semicolon ';' am Ende eines 'include' Statement erwartet.");
72
73 if (tokenStream[2].Type != TokenType.SemiColon)
74 throw new ParserException("Semicolon ';' am Ende eines 'include' Statement erwartet.");
75
76 if (tokenStream.Count > 3)
77 throw new ParserException("Es wird nichts nach dem Semicolon ';' am Ende eines 'include' Statement erwartet.");
78
79 string include = (string)tokenStream[1].Lexeme;
80
81 sourceCode.RemoveAt(i);
82
83 if (included.ContainsKey(include))
84 continue;
85
86 /* and place the source where the original include statement was.. */
87 sourceCode.InsertRange(i, scanner.Scan(include));
88
89 /* set the current script as already included */
90 included[include] = true;
91
92 --i;
93
94 }
95
96 }
97
98 #endregion
99
100 #region Public Methods
101
102 private static object ConvertLexeme(SerializableToken st)
103 {
104 if (st.Lexeme == null)
105 return null;
106
107 // Je nach TokenType anders parsen
108 switch (st.Type)
109 {
110 case TokenType.Integer:
111 // z.B. "42" -> 42
112 return int.Parse(st.Lexeme, CultureInfo.InvariantCulture);
113
114 case TokenType.Float:
115 // z.B. "3.14" -> 3.14f
116 return float.Parse(st.Lexeme, CultureInfo.InvariantCulture);
117
118 case TokenType.Double:
119 // z.B. "3.14159" -> 3.14159d
120 return double.Parse(st.Lexeme, CultureInfo.InvariantCulture);
121
122 case TokenType.Boolean:
123 // "true" / "false"
124 return bool.Parse(st.Lexeme);
125
126 case TokenType.Char:
127 // hängt davon ab, wie dein Lexer das Lexeme speichert
128 // Beispiel 1: nur das Zeichen selbst: "a"
129 if (st.Lexeme.Length == 1)
130 return st.Lexeme[0];
131
132 // Beispiel 2: inklusive Hochkommas: "'a'"
133 if (st.Lexeme.Length >= 3 &&
134 st.Lexeme[0] == '\'' &&
135 st.Lexeme[^1] == '\'')
136 {
137 return st.Lexeme[1];
138 }
139
140 throw new FormatException($"Ungültiges Char-Lexeme: '{st.Lexeme}'");
141
142 case TokenType.Null:
143 // für 'null' kannst du einfach null zurückgeben
144 return null;
145
146 case TokenType.String:
147 // hier kannst du entscheiden:
148 // - komplette Literal-Syntax behalten ("\"Hallo\"")
149 // - oder die Anführungszeichen entfernen, wenn dein Lexer sie drin hat
150 // Beispiel: Anführungszeichen entfernen:
151 if (st.Lexeme.Length >= 2 &&
152 st.Lexeme[0] == '"' &&
153 st.Lexeme[^1] == '"')
154 {
155 return st.Lexeme.Substring(1, st.Lexeme.Length - 2);
156 }
157 return st.Lexeme;
158
159 default:
160 // für alles andere reicht der String
161 return st.Lexeme;
162 }
163 }
164
165 private static object ConvertLexeme(TokenType type, string lexemeStr)
166 {
167 // Falls du für Null-Tokens z.B. ein leeres Lexeme speicherst:
168 if (type == TokenType.Null)
169 return null;
170
171 if (lexemeStr == null)
172 return null;
173
174 switch (type)
175 {
176 case TokenType.Integer:
177 return int.Parse(lexemeStr, CultureInfo.InvariantCulture);
178
179 case TokenType.Float:
180 return float.Parse(lexemeStr, CultureInfo.InvariantCulture);
181
182 case TokenType.Double:
183 return double.Parse(lexemeStr, CultureInfo.InvariantCulture);
184
185 case TokenType.Boolean:
186 return bool.Parse(lexemeStr);
187
188 case TokenType.Char:
189 // Variante 1: nur das Zeichen selbst gespeichert, z.B. "a"
190 if (lexemeStr.Length == 1)
191 return lexemeStr[0];
192
193 // Variante 2: inkl. Hochkommas, z.B. "'a'"
194 if (lexemeStr.Length >= 3 &&
195 lexemeStr[0] == '\'' &&
196 lexemeStr[^1] == '\'')
197 {
198 return lexemeStr[1];
199 }
200
201 throw new FormatException($"Ungültiges Char-Lexeme: '{lexemeStr}'");
202
203 case TokenType.String:
204 // Wenn dein Lexer die Anführungszeichen mit speichert ("\"Hallo\"")
205 if (lexemeStr.Length >= 2 &&
206 lexemeStr[0] == '"' &&
207 lexemeStr[^1] == '"')
208 {
209 return lexemeStr.Substring(1, lexemeStr.Length - 2);
210 }
211 return lexemeStr;
212
213 default:
214 // Für alles andere reicht der String
215 return lexemeStr;
216 }
217 }
218
219 public Script(Manager manager, string scriptName)
220 {
221
222 this.manager = manager;
223
224 this.scriptName = scriptName;
225
226 try
227 {
228
229 Scan(scriptName);
230
231 Lexer lexer = new Lexer(sourceCode);
232
233 List<Token> tokenStream = lexer.GetTokens();
234
235 Parser parser = new Parser(this, tokenStream);
236
237 parser.DebugMode = this.manager.Debug;
238
239 executable = parser.Parse();
240
241 if (this.manager.Optimize)
242 {
243
244 Optimizer optimizer = new Optimizer(executable);
245
246 optimizer.OptimizerInfo = false;
247
248 optimizer.Optimize();
249
250 }
251
252
253 }
254 catch (Exception exception)
255 {
256 throw new ScriptStackException("Fehler in '" + scriptName + "'.", exception);
257 }
258
259 }
260
261 public Script(Manager manager, string scriptName, bool binary)
262 {
263
264 this.manager = manager;
265
266 this.scriptName = scriptName;
267
268 try
269 {
270
271 List<Token> tokenStream = null;
272 if (binary == true)
273 {
274 tokenStream = new List<Token>();
275 using (var fs = new FileStream(scriptName, FileMode.Open, FileAccess.Read))
276 using (var br = new BinaryReader(fs))
277 {
278 int count = br.ReadInt32();
279
280 for (int i = 0; i < count; i++)
281 {
282 var typeInt = br.ReadInt32();
283 var lexemeStr = br.ReadString();
284 var line = br.ReadInt32();
285 var column = br.ReadInt32();
286 var text = ""; // br.ReadString();
287
288 TokenType type = (TokenType)typeInt;
289 object lexeme = ConvertLexeme(type, lexemeStr);
290
291 tokenStream.Add(new Token(type, lexeme, line, column, text));
292 }
293 }
294 }
295 else
296 {
297 string json = File.ReadAllText(scriptName, Encoding.UTF8);
298
299 var serializableTokens = JsonSerializer.Deserialize<List<SerializableToken>>(json);
300
301 // Falls du wieder echte Token-Objekte brauchst:
302 tokenStream = serializableTokens
303 .Select(st => new Token(
304 st.Type,
305 ConvertLexeme(st), // (st.Lexeme) oder hier wieder in int/float/etc. umwandeln, wenn nötig
306 st.Line,
307 st.Column,
308 st.Text))
309 .ToList();
310 }
311
312 Parser parser = new Parser(this, tokenStream);
313
314 parser.DebugMode = this.manager.Debug;
315
316 executable = parser.Parse();
317
318
319 if (this.manager.Optimize)
320 {
321
322 Optimizer optimizer = new Optimizer(executable);
323
324 optimizer.OptimizerInfo = false;
325
326 optimizer.Optimize();
327
328 }
329
330
331 }
332 catch (Exception exception)
333 {
334 throw new ScriptStackException("Fehler in '" + scriptName + "'.", exception);
335 }
336
337 }
338
339 public void CompileBinary(string fileName)
340 {
341
342 try
343 {
344
345 Scan(scriptName);
346
347 Lexer lexer = new Lexer(sourceCode);
348
349 List<Token> tokenStream = lexer.GetTokens();
350
351 using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
352 using (var bw = new BinaryWriter(fs))
353 {
354 // Anzahl Tokens zuerst
355 bw.Write(tokenStream.Count);
356
357 foreach (var t in tokenStream)
358 {
359 bw.Write((int)t.Type); // Enum als int
360 bw.Write(t.Lexeme?.ToString() ?? ""); // als String
361 bw.Write(t.Line);
362 bw.Write(t.Column);
363 //bw.Write(t.Text ?? "");
364 }
365 }
366
367 }
368 catch (Exception exception)
369 {
370 throw new ScriptStackException("Fehler in '" + scriptName + "'.", exception);
371 }
372 }
373
374 public void CompileJSON(string fileName)
375 {
376
377 try
378 {
379
380 Scan(scriptName);
381
382 Lexer lexer = new Lexer(sourceCode);
383
384 List<Token> tokenStream = lexer.GetTokens();
385
386 var serializableTokens = tokenStream.Select(t => new SerializableToken
387 {
388 Type = t.Type,
389 Lexeme = t.Lexeme?.ToString(),
390 Line = t.Line,
391 Column = t.Column,
392 Text = t.Text
393 }).ToList();
394
395 var options = new JsonSerializerOptions
396 {
397 WriteIndented = true // für schön formatiertes JSON
398 };
399
400 string json = JsonSerializer.Serialize(serializableTokens, options);
401
402 // In Datei schreiben
403 File.WriteAllText(fileName, json, Encoding.UTF8);
404
405 }
406 catch (Exception exception)
407 {
408 throw new ScriptStackException("Fehler in '" + scriptName + "'.", exception);
409 }
410 }
411
412 public bool EntryPoint()
413 {
414 return executable.FunctionExists("main");
415 }
416
417 #endregion
418
419 #region Public Properties
420
422 {
423 get { return manager; }
424 }
425
426 public string Name
427 {
428 get { return scriptName; }
429 }
430
431 public ReadOnlyCollection<String> SourceLines
432 {
433 get { return sourceCode.AsReadOnly(); }
434 }
435
436 public string Source
437 {
438 get
439 {
440 StringBuilder sb = new StringBuilder();
441 foreach (string line in sourceCode)
442 {
443 sb.Append(line);
444 sb.Append("\r\n");
445 }
446 return sb.ToString();
447 }
448 }
449
451 {
452 get { return executable; }
453 }
454
456 {
457 get { return executable.ScriptMemory; }
458 }
459
460 public ScriptStack.Collections.ReadOnlyDictionary<String, Function> Functions
461 {
462 get
463 {
464 return new ScriptStack.Collections.ReadOnlyDictionary<String,Function>(executable.Functions);
465 }
466 }
467
469 {
470 get { return executable.MainFunction; }
471 }
472
473 #endregion
474
475 }
476}
477
The lexical analyzer (Lexer) breaks code (written in sentences) into a series of known Token and pass...
Definition Lexer.cs:21
List< Token > GetTokens()
Definition Lexer.cs:140
The parser builds an ScriptStack.Runtime.Executable out of the Token stream returned from the ScriptS...
Definition Parser.cs:22
Executable Parse()
Parse the token stream into an executable.
Definition Parser.cs:3092
A lexical token or simply token is a string with an assigned and thus identified meaning.
Definition Token.cs:100
A function, forward declared in a script.
Definition Function.cs:15
Script(Manager manager, string scriptName, bool binary)
Definition Script.cs:261
void CompileJSON(string fileName)
Definition Script.cs:374
Script(Manager manager, string scriptName)
Definition Script.cs:219
void CompileBinary(string fileName)
Definition Script.cs:339
ScriptStack.Collections.ReadOnlyDictionary< String, Function > Functions
Definition Script.cs:461
ReadOnlyCollection< String > SourceLines
Definition Script.cs:432
An interface to modify the default process of reading text files into Script's.
Definition Scanner.cs:116
List< String > Scan(String strResourceName)
TokenType
Known types of Token.
Definition Token.cs:12