using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using NTERA.Interpreter; using NTERA.Interpreter.Compiler; namespace NTERA.Compiler { public class Compiler { public string InputDirectory { get; } public List> Errors { get; } = new List>(); public List> Warnings { get; } = new List>(); public Dictionary DeclaredFunctions = new Dictionary(); public Compiler(string inputDirectory) { InputDirectory = inputDirectory; } protected static string[] DefaultGlobalNumberVariables = { "LOCAL", "ABL", "RESULT", "SOURCE", "ARG", "GLOBAL", "IS", "CUP", "CDOWN", "UP", "DOWN", "DOWNBASE", "COUNT", "TCVAR", "CDFLAG", "ITEMPRICE", "RANDDATA", "LINECOUNT", "ISTIMEOUT", "__INT_MAX__", "__INT_MIN__", "RAND", "CHARANUM", "TALENT", "FLAG", "TFLAG", "CFLAG", "MASTER", "BASE", "MAXBASE", "PALAM", "TEQUIP", "EQUIP", "DAY", "MARK", "PALAMLV", "TARGET", "PLAYER", "NOWEX", "EX", "STAIN", "EXP", "ASSIPLAY", "ASSI", "ITEM", "EXPLV", "TIME", "MONEY", "SELECTCOM", "PREVCOM", "NEXTCOM", "DITEMTYPE", "NO", "RELATION", "JUEL", "GOTJUEL", "EJAC", "BOUGHT", "ITEMSALES", "FORWARD", //special casing for SORTCHARA "BACK", //special casing for SORTCHARA "DEBUG_MODE", }; protected static string[] DefaultGlobalStringVariables = { "LOCALS", "ARGS", "RESULTS", "CSTR", "GLOBALS", "NICKNAME", "MASTERNAME", "NAME", "TRAINNAME", "BASENAME", "EQUIPNAME", "TEQUIPNAME", "STAINNAME", "EXNAME", "SOURCENAME", "CALLNAME", "FLAGNAME", "TFLAGNAME", "CFLAGNAME", "TCVARNAME", "STRNAME", "TSTRNAME", "CSTRNAME", "SAVESTRNAME", "CDFLAGNAME", "GLOBALNAME", "GLOBALSNAME", "GAMEBASE_AUTHOR", "GAMEBASE_INFO", "GAMEBASE_YEAR", "GAMEBASE_TITLE", "GAMEBASE_GAMECODE", "GAMEBASE_VERSION", "GAMEBASE_ALLOWVERSION", "GAMEBASE_DEFAULTCHARA", "GAMEBASE_NOITEM", "WINDOW_TITLE", "MONEYLABEL", "DRAWLINESTR", "LASTLOAD_VERSION", "LASTLOAD_NO", "LASTLOAD_TEXT", "SAVEDATA_TEXT", "TSTR", "STR", "SAVESTR", "ABLNAME", "MARKNAME", "TALENTNAME", "ITEMNAME", "PALAMNAME", "EXPNAME", }; public void Compile(string outputDirectory) { var globals = new VariableDictionary(); foreach (string stringVariable in DefaultGlobalStringVariables) globals.Add(stringVariable, ""); foreach (string numberVariable in DefaultGlobalNumberVariables) globals.Add(numberVariable, 0); foreach (char c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ") globals[c.ToString()] = ""; var funcs = new List { new FunctionDefinition("ABS", new[] { new FunctionParameter("a", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("CSVCALLNAME", new[] { new FunctionParameter("a", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("TOFULL", new[] { new FunctionParameter("a", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("TOINT", new[] { new FunctionParameter("a", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("RAND", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("TOSTR", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0], "") }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("STRCOUNT", new[] { new FunctionParameter("input", new string[0]), new FunctionParameter("match", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("MIN", new[] { new FunctionParameter("a", new string[0], isArrayParameter: true), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("MAX", new[] { new FunctionParameter("a", new string[0], isArrayParameter: true), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("POWER", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("GETPALAMLV", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("GETBIT", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("UNICODE", new[] { new FunctionParameter("a", new string[0]) }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("MATCH", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]), new FunctionParameter("c", new string[0], "a"), new FunctionParameter("d", new string[0], "f"), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("INRANGE", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("b", new string[0]), new FunctionParameter("c", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("HE_SHE", new[] { new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("SUBSTRING", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("STRLENS", new[] { new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("CSVNAME", new[] { new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("CSVNAME", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("CSVBASE", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("CSVTALENT", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("CSVCSTR", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("CSVEXP", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("CSVABL", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("GETNUM", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("FINDCHARA", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("LIMIT", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("SUBSTRINGU", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0]), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("GROUPMATCH", new[] { new FunctionParameter("a", new string[0]), new FunctionParameter("a", new string[0], isArrayParameter: true), }, new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("GETTIME", new FunctionParameter[0], new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("SAVENOS", new FunctionParameter[0], new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("GETCOLOR", new FunctionParameter[0], new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("CURRENTREDRAW", new FunctionParameter[0], new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("GETFONT", new FunctionParameter[0], new FunctionVariable[0], true, "_GLOBAL", new Marker()), new FunctionDefinition("GETMILLISECOND", new FunctionParameter[0], new FunctionVariable[0], true, "_GLOBAL", new Marker()), }; var stringStatements = new List { "DRAWLINEFORM", "PRINTFORML", "PRINT", "PRINTW", "PRINTV", "PRINTL", "PRINTLC", "PRINTC", "ALIGNMENT", "CALL", "CUSTOMDRAWLINE", "DATAFORM", "GOTO", "PRINTFORMDL", "PRINTFORMW", "PRINTFORMDW", "PRINTFORMC", "PRINTFORMLC", "PRINTFORM", "PRINTPLAINFORM", "DEBUGPRINTL", "DEBUGPRINTFORML", "REUSELASTLINE", "PRINTPLAIN", "PRINT_TRAIN_NAME", "PRINT_STR", }; string csvPath = Path.Combine(InputDirectory, "CSV"); string erbPath = Path.Combine(InputDirectory, "ERB"); var csvDefinition = new CSVDefinition(); foreach (var file in Directory.EnumerateFiles(csvPath, "*.csv", SearchOption.AllDirectories)) { string path = Path.GetFileNameWithoutExtension(file); if (path.EndsWith("_TR")) continue; Preprocessor.ProcessCSV(csvDefinition, path, File.ReadLines(file, Encoding.UTF8)); } ConcurrentDictionary preprocessedFunctions = new ConcurrentDictionary(); #if DEBUG foreach (var file in Directory.EnumerateFiles(erbPath, "*.erb", SearchOption.AllDirectories)) #else Parallel.ForEach(Directory.EnumerateFiles(erbPath, "*.erb", SearchOption.AllDirectories), new ParallelOptions { MaxDegreeOfParallelism = 4 }, file => #endif { try { foreach (var kv in Preprocessor.PreprocessFile(File.ReadAllText(file), Path.GetFileName(file))) preprocessedFunctions[kv.Key] = kv.Value; } catch (Exception ex) { lock (Errors) Errors.Add(new Tuple(new ParserError($"Internal pre-process lexer error [{ex}]", new Marker()), Path.GetFileName(file))); } #if DEBUG } #else }); #endif DeclaredFunctions = preprocessedFunctions.ToDictionary(kv => kv.Key, kv => kv.Value); funcs.AddRange(DeclaredFunctions.Keys.Where(x => x.IsReturnFunction)); var procedures = DeclaredFunctions.Keys.Where(x => !x.IsReturnFunction).ToList(); #if DEBUG foreach (var kv in DeclaredFunctions) #else Parallel.ForEach(DeclaredFunctions, new ParallelOptions { MaxDegreeOfParallelism = 4 }, kv => #endif { try { var locals = new VariableDictionary(); foreach (var param in kv.Key.Variables) locals[param.Name] = param.DefaultValue ?? param.ValueType == Interpreter.ValueType.String ? new Value("") : new Value(0d); Parser parser = new Parser(kv.Value, kv.Key, funcs, procedures, globals, locals, stringStatements, csvDefinition); var nodes = parser.Parse(out var localErrors, out var localWarnings); lock (Errors) Errors.AddRange(localErrors.Select(x => new Tuple(x, $"{kv.Key.Filename} - {kv.Key.Name}"))); lock (Warnings) Warnings.AddRange(localWarnings.Select(x => new Tuple(x, $"{kv.Key.Filename} - {kv.Key.Name}"))); } catch (Exception ex) { lock (Errors) Errors.Add(new Tuple(new ParserError($"Internal post-process lexer error [{ex}]", new Marker()), $"{kv.Key.Filename} - {kv.Key.Name}")); } #if DEBUG } #else }); #endif long fileSize = 0; int fileCount = 0; foreach (var file in Directory.EnumerateFiles(InputDirectory, "*.erb", SearchOption.AllDirectories)) { var info = new FileInfo(file); fileSize += info.Length; fileCount++; } var htmlWriter = new HTMLWriter { Errors = Errors.OrderBy(x => x.Item2).ToList(), Warnings = Warnings.OrderBy(x => x.Item2).ToList(), FunctionCount = DeclaredFunctions.Count, TotalFileSize = fileSize, TotalFileCount = fileCount }; using (var stream = File.Create(Path.Combine(outputDirectory, "report.html"))) htmlWriter.WriteReportToFile(stream); } } }