using System.IO; using System.Collections.Generic; using System.Globalization; using CodeImp.DoomBuilder.ZDoom; using CodeImp.DoomBuilder.GZBuilder.Data; //mxd. ACS parser used to create ScriptItems for use in script editor's navigator namespace CodeImp.DoomBuilder.GZBuilder.GZDoom { internal sealed class AcsParserSE : ZDTextParser { internal delegate void IncludeDelegate(AcsParserSE parser, string includefile); internal IncludeDelegate OnInclude; private readonly HashSet parsedlumps; private readonly HashSet includes; private List includestoskip; private readonly List namedscripts; private readonly List numberedscripts; private readonly List functions; internal List NamedScripts { get { return namedscripts; } } internal List NumberedScripts { get { return numberedscripts; } } internal List Functions { get { return functions; } } internal IEnumerable Includes { get { return includes; } } internal AcsParserSE() { namedscripts = new List(); numberedscripts = new List(); functions = new List(); parsedlumps = new HashSet(); includes = new HashSet(); includestoskip = new List(); specialtokens += "(,)"; } public override bool Parse(Stream stream, string sourcefilename) { return Parse(stream, sourcefilename, new List(), false, false); } public bool Parse(Stream stream, string sourcefilename, bool processincludes, bool isinclude) { return Parse(stream, sourcefilename, includestoskip, processincludes, isinclude); } public bool Parse(Stream stream, string sourcefilename, List configincludes, bool processincludes, bool isinclude) { // Integrity check if(stream == null || stream.Length == 0) { ReportError("Unable to load " + (isinclude ? "include" : "") + " file '" + sourcefilename + "'!"); return false; } base.Parse(stream, sourcefilename); // Already parsed this? if(parsedlumps.Contains(sourcefilename)) return false; parsedlumps.Add(sourcefilename); if(isinclude && !includes.Contains(sourcefilename)) includes.Add(sourcefilename); includestoskip = configincludes; int bracelevel = 0; // Keep local data Stream localstream = datastream; string localsourcename = sourcename; BinaryReader localreader = datareader; // Continue until at the end of the stream while(SkipWhitespace(true)) { string token = ReadToken(); if(string.IsNullOrEmpty(token)) continue; // Ignore inner scope stuff if(token == "{") { bracelevel++; continue; } if(token == "}") { bracelevel--; continue; } if(bracelevel > 0) continue; switch(token.ToLowerInvariant()) { case "script": { SkipWhitespace(true); int startpos = (int)stream.Position; token = ReadToken(); //is it named script? if(token.IndexOf('"') != -1) { startpos += 1; string scriptname = StripTokenQuotes(token); // Try to parse argument names List> args = ParseArgs(); List argnames = new List(); foreach(KeyValuePair group in args) argnames.Add(group.Value); // Add to collection namedscripts.Add(new ScriptItem(scriptname, argnames, startpos, isinclude)); } else //should be numbered script { int n; if(int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out n)) { // Try to parse argument names List> args = ParseArgs(); // Now find the opening brace do { if(!SkipWhitespace(true)) break; token = ReadToken(); } while (!string.IsNullOrEmpty(token) && token != "{"); token = ReadLine(); string name = ""; bracelevel = 1; if(!string.IsNullOrEmpty(token)) { int commentstart = token.IndexOf("//"); if(commentstart != -1) //found comment { commentstart += 2; name = token.Substring(commentstart, token.Length - commentstart).Trim(); } } bool customname = (name.Length > 0); name = (customname ? name + " [" + n + "]" : "Script " + n); List argnames = new List(); foreach(KeyValuePair group in args) argnames.Add(group.Value); // Add to collection numberedscripts.Add(new ScriptItem(n, name, argnames, startpos, isinclude, customname)); } } } break; case "function": { SkipWhitespace(true); int startpos = (int)stream.Position; string funcname = ReadToken(); //read return type SkipWhitespace(true); funcname += " " + ReadToken(); //read function name // Try to parse argument names List> args = ParseArgs(); List argnames = new List(); foreach(KeyValuePair group in args) argnames.Add(group.Value); // Add to collection functions.Add(new ScriptItem(funcname, argnames, startpos, isinclude)); } break; default: if(processincludes && (token == "#include" || token == "#import")) { SkipWhitespace(true); string includelump = StripTokenQuotes(ReadToken()).ToLowerInvariant(); if(!string.IsNullOrEmpty(includelump)) { string includename = Path.GetFileName(includelump); if(includestoskip.Contains(includename) || includes.Contains(includename)) continue; // Callback to parse this file if(OnInclude != null) OnInclude(this, includelump.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)); // Set our buffers back to continue parsing datastream = localstream; datareader = localreader; sourcename = localsourcename; } else { ReportError("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": got #include directive without include path!"); return false; } } break; } } return true; } private List> ParseArgs() //type, name { List> argnames = new List>(); SkipWhitespace(true); string token = ReadToken(); // Should be ENTER/OPEN etc. script type if(token != "(") { argnames.Add(new KeyValuePair(token.ToUpperInvariant(), string.Empty)); return argnames; } while(SkipWhitespace(true)) { string argtype = ReadToken(); // should be type if(IsSpecialToken(argtype)) break; if(argtype.ToUpperInvariant() == "VOID") { argnames.Add(new KeyValuePair("(void)", string.Empty)); break; } SkipWhitespace(true); token = ReadToken(); // should be arg name argnames.Add(new KeyValuePair(argtype, token)); SkipWhitespace(true); token = ReadToken(); // should be comma or ")" if(token != ",") break; } return argnames; } } }