ScriptStack 1.0.5
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
24 private List<String> sourceCode;
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
220 {
221
222 this.manager = manager;
223
224 this.scriptName = scriptName;
225
226 try
227 {
228
230
231 //Lexer lexer = new Lexer(sourceCode);
232 Lexer lexer = manager.LexerFactory(sourceCode);
233
234 List<Token> tokenStream = lexer.GetTokens();
235
236 Parser parser = new Parser(this, tokenStream);
237
238 parser.DebugMode = this.manager.Debug;
239
240 executable = parser.Parse();
241
242 if (this.manager.Optimize)
243 {
244
245 Optimizer optimizer = new Optimizer(executable);
246
247 optimizer.OptimizerInfo = false;
248
249 optimizer.Optimize();
250
251 }
252
253
254 }
255 catch (Exception exception)
256 {
257 throw new ScriptStackException("Fehler in '" + scriptName + "'.", exception);
258 }
259
260 }
261
262 public Script(Manager manager, string scriptName, bool binary)
263 {
264
265 this.manager = manager;
266
267 this.scriptName = scriptName;
268
269 try
270 {
271
272 List<Token> tokenStream = null;
273 if (binary == true)
274 {
275 tokenStream = new List<Token>();
276 using (var fs = new FileStream(scriptName, FileMode.Open, FileAccess.Read))
277 using (var br = new BinaryReader(fs))
278 {
279 int count = br.ReadInt32();
280
281 for (int i = 0; i < count; i++)
282 {
283 var typeInt = br.ReadInt32();
284 var lexemeStr = br.ReadString();
285 var line = br.ReadInt32();
286 var column = br.ReadInt32();
287 var text = ""; // br.ReadString();
288
289 TokenType type = (TokenType)typeInt;
290 object lexeme = ConvertLexeme(type, lexemeStr);
291
292 tokenStream.Add(new Token(type, lexeme, line, column, text));
293 }
294 }
295 }
296 else
297 {
298 string json = File.ReadAllText(scriptName, Encoding.UTF8);
299
300 var serializableTokens = JsonSerializer.Deserialize<List<SerializableToken>>(json);
301
302 // Falls du wieder echte Token-Objekte brauchst:
303 tokenStream = serializableTokens
304 .Select(st => new Token(
305 st.Type,
306 ConvertLexeme(st), // (st.Lexeme) oder hier wieder in int/float/etc. umwandeln, wenn nötig
307 st.Line,
308 st.Column,
309 st.Text))
310 .ToList();
311 }
312
313 Parser parser = new Parser(this, tokenStream);
314
315 parser.DebugMode = this.manager.Debug;
316
317 executable = parser.Parse();
318
319
320 if (this.manager.Optimize)
321 {
322
323 Optimizer optimizer = new Optimizer(executable);
324
325 optimizer.OptimizerInfo = false;
326
327 optimizer.Optimize();
328
329 }
330
331
332 }
333 catch (Exception exception)
334 {
335 throw new ScriptStackException("Fehler in '" + scriptName + "'.", exception);
336 }
337
338 }
339
340 public void CompileBinary(string fileName)
341 {
342
343 try
344 {
345
347
348 Lexer lexer = new Lexer(sourceCode);
349
350 List<Token> tokenStream = lexer.GetTokens();
351
352 using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
353 using (var bw = new BinaryWriter(fs))
354 {
355 // Anzahl Tokens zuerst
356 bw.Write(tokenStream.Count);
357
358 foreach (var t in tokenStream)
359 {
360 bw.Write((int)t.Type); // Enum als int
361 bw.Write(t.Lexeme?.ToString() ?? ""); // als String
362 bw.Write(t.Line);
363 bw.Write(t.Column);
364 //bw.Write(t.Text ?? "");
365 }
366 }
367
368 }
369 catch (Exception exception)
370 {
371 throw new ScriptStackException("Fehler in '" + scriptName + "'.", exception);
372 }
373 }
374
375 public void CompileJSON(string fileName)
376 {
377
378 try
379 {
380
382
383 Lexer lexer = new Lexer(sourceCode);
384
385 List<Token> tokenStream = lexer.GetTokens();
386
387 var serializableTokens = tokenStream.Select(t => new SerializableToken
388 {
389 Type = t.Type,
390 Lexeme = t.Lexeme?.ToString(),
391 Line = t.Line,
392 Column = t.Column,
393 Text = t.Text
394 }).ToList();
395
396 var options = new JsonSerializerOptions
397 {
398 WriteIndented = true // für schön formatiertes JSON
399 };
400
401 string json = JsonSerializer.Serialize(serializableTokens, options);
402
403 // In Datei schreiben
404 File.WriteAllText(fileName, json, Encoding.UTF8);
405
406 }
407 catch (Exception exception)
408 {
409 throw new ScriptStackException("Fehler in '" + scriptName + "'.", exception);
410 }
411 }
412
413 public bool EntryPoint()
414 {
415 return executable.FunctionExists("main");
416 }
417
418 #endregion
419
420 #region Public Properties
421
423 {
424 get { return manager; }
425 }
426
427 public string Name
428 {
429 get { return scriptName; }
430 }
431
432 public ReadOnlyCollection<String> SourceLines
433 {
434 get { return sourceCode.AsReadOnly(); }
435 }
436
437 public string Source
438 {
439 get
440 {
441 StringBuilder sb = new StringBuilder();
442 foreach (string line in sourceCode)
443 {
444 sb.Append(line);
445 sb.Append("\r\n");
446 }
447 return sb.ToString();
448 }
449 }
450
452 {
453 get { return executable; }
454 }
455
457 {
458 get { return executable.ScriptMemory; }
459 }
460
461 public ScriptStack.Collections.ReadOnlyDictionary<String, Function> Functions
462 {
463 get
464 {
465 return new ScriptStack.Collections.ReadOnlyDictionary<String,Function>(executable.Functions);
466 }
467 }
468
470 {
471 get { return executable.MainFunction; }
472 }
473
474 #endregion
475
476 }
477}
478
479
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:177
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:3355
A lexical token or simply token is a string with an assigned and thus identified meaning.
Definition Token.cs:101
A function, forward declared in a script.
Definition Function.cs:15
List< String > sourceCode
Definition Script.cs:24
static object ConvertLexeme(SerializableToken st)
Definition Script.cs:102
Script(Manager manager, string scriptName, bool binary)
Definition Script.cs:262
static object ConvertLexeme(TokenType type, string lexemeStr)
Definition Script.cs:165
void CompileJSON(string fileName)
Definition Script.cs:375
void Scan(string scriptName)
Definition Script.cs:31
Script(Manager manager, string scriptName)
Definition Script.cs:219
void CompileBinary(string fileName)
Definition Script.cs:340
ScriptStack.Collections.ReadOnlyDictionary< String, Function > Functions
Definition Script.cs:462
ReadOnlyCollection< String > SourceLines
Definition Script.cs:433
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