using System.Collections.Generic; using Cthangover.Mods.Dialog.Scenarios; using Cthangover.Core.Scenarios; using Cthangover.Core.Factories.Impls; using Cthangover.Core.UI.Theming; using Godot; namespace Cthangover.Tools.Services { /// /// Static service providing scenario DSL syntax highlighting, tokenisation, /// and command-line parsing. produces a /// for the scenario editor's /// . tokenises a /// scenario command line into a command name, positional arguments, and /// named (key=value) parameters — used by /// for reference analysis. /// public static class ScenarioSyntaxService { /// /// Builds a for scenario DSL syntax. /// Comments (lines starting with #) are green, strings are bright /// gold, command names are blue, and argument keywords (e.g. key, /// wait, quest_id) are amber — all from . /// public static CodeHighlighter CreateHighlighter() { var hl = new CodeHighlighter(); hl.AddColorRegion("#", null, UiTokens.Success, true); hl.AddColorRegion("\"", "\"", UiTokens.AccentBright, false); hl.NumberColor = UiTokens.Info; hl.SymbolColor = UiTokens.Muted; var cmdColor = UiTokens.Info; hl.KeywordColors = new Godot.Collections.Dictionary(); foreach (var cmd in ScenarioCommandStrategyFactory.GetAllCommandNames()) hl.KeywordColors[cmd] = cmdColor; foreach (var cmd in new[] { "option", "select" }) hl.KeywordColors[cmd] = cmdColor; var argColor = UiTokens.Accent; foreach (var kw in new[] { "key", "first", "second", "hide_color", "wait", "speed", "hidden", "sprites", "loop", "target", "quest_id", "show", "hide", "duration" }) hl.KeywordColors[kw] = argColor; return hl; } /// /// Splits a line into tokens, handling quoted strings (with backslash /// escaping) and whitespace. Returns all non-whitespace tokens in order. /// public static List Tokenize(string line) { var result = new List(); var i = 0; while (i < line.Length) { if (char.IsWhiteSpace(line[i])) { i++; continue; } if (line[i] == '"') { i++; var start = i; while (i < line.Length && line[i] != '"') { if (line[i] == '\\' && i + 1 < line.Length) i++; i++; } result.Add(line.Substring(start, i - start)); if (i < line.Length) i++; continue; } var tok = i; while (i < line.Length && !char.IsWhiteSpace(line[i])) i++; result.Add(line.Substring(tok, i - tok)); } return result; } /// Strips surrounding double quotes from a value if present. public static string StripQuotes(string val) { if (val.Length >= 2 && val.StartsWith("\"") && val.EndsWith("\"")) return val.Substring(1, val.Length - 2); return val; } /// /// Parses a scenario command line into its components. Tokenises via /// , then extracts the first token as the command /// name (lowercased), remaining bare tokens as positional arguments, and /// key=value pairs as named arguments. The -> arrow token /// is consumed but discarded. /// /// A single scenario command line (trimmed). /// The command name, lowercased. /// Positional arguments in order. /// Named arguments keyed by lowercased name. public static void ExtractCommandParams(string line, out string cmd, out List positional, out Dictionary named) { positional = new List(); named = new Dictionary(System.StringComparer.OrdinalIgnoreCase); cmd = ""; var tokens = Tokenize(line); if (tokens.Count == 0) return; cmd = tokens[0].ToLowerInvariant(); for (int t = 1; t < tokens.Count; t++) { var tk = tokens[t]; if (tk == "->") { t++; continue; } var eq = tk.IndexOf('='); if (eq > 0) named[tk.Substring(0, eq).ToLowerInvariant()] = StripQuotes(tk.Substring(eq + 1)); else positional.Add(tk); } } } }