public static CompilationResult Compile(CompilationJob compilationJob) { var results = new List <CompilationResult>(); // All variable declarations that we've encountered during this // compilation job var derivedVariableDeclarations = new List <Declaration>(); // All variable declarations that we've encountered, PLUS the // ones we knew about before var knownVariableDeclarations = new List <Declaration>(); if (compilationJob.VariableDeclarations != null) { knownVariableDeclarations.AddRange(compilationJob.VariableDeclarations); } // Get function declarations from the library, if provided if (compilationJob.Library != null) { knownVariableDeclarations.AddRange(GetDeclarationsFromLibrary(compilationJob.Library)); } var compiledTrees = new List <(string name, IParseTree tree)>(); // First pass: parse all files, generate their syntax trees, // and figure out what variables they've declared var stringTableManager = new StringTableManager(); foreach (var file in compilationJob.Files) { var tree = ParseSyntaxTree(file); compiledTrees.Add((file.FileName, tree)); RegisterStrings(file.FileName, stringTableManager, tree); } if (compilationJob.CompilationType == CompilationJob.Type.StringsOnly) { // Stop at this point return(new CompilationResult { Declarations = null, ContainsImplicitStringTags = stringTableManager.ContainsImplicitStringTags, Program = null, StringTable = stringTableManager.StringTable, }); } var fileTags = new Dictionary <string, IEnumerable <string> >(); foreach (var parsedFile in compiledTrees) { GetDeclarations(parsedFile.name, parsedFile.tree, knownVariableDeclarations, out var newDeclarations, out var newFileTags); derivedVariableDeclarations.AddRange(newDeclarations); knownVariableDeclarations.AddRange(newDeclarations); fileTags.Add(parsedFile.name, newFileTags); } foreach (var parsedFile in compiledTrees) { var checker = new TypeCheckVisitor(parsedFile.name, knownVariableDeclarations); checker.Visit(parsedFile.tree); derivedVariableDeclarations.AddRange(checker.NewDeclarations); knownVariableDeclarations.AddRange(checker.NewDeclarations); } if (compilationJob.CompilationType == CompilationJob.Type.DeclarationsOnly) { // Stop at this point return(new CompilationResult { Declarations = derivedVariableDeclarations, ContainsImplicitStringTags = false, Program = null, StringTable = null, FileTags = fileTags, }); } foreach (var parsedFile in compiledTrees) { CompilationResult compilationResult = GenerateCode(parsedFile.name, knownVariableDeclarations, compilationJob, parsedFile.tree, stringTableManager); results.Add(compilationResult); } var finalResult = CompilationResult.CombineCompilationResults(results, stringTableManager); // Last step: take every variable declaration we found in all // of the inputs, and create an initial value registration for // it. foreach (var declaration in knownVariableDeclarations) { // We only care about variable declarations here if (declaration.DeclarationType != Declaration.Type.Variable) { continue; } Operand value; if (declaration.DeclarationType == Declaration.Type.Variable && declaration.defaultValue == null) { throw new NullReferenceException($"Variable declaration {declaration.name} ({declaration.ReturnType}) has a null default value. This is not allowed."); } switch (declaration.ReturnType) { case Yarn.Type.Number: value = new Operand(Convert.ToSingle(declaration.DefaultValue)); break; case Yarn.Type.String: value = new Operand(Convert.ToString(declaration.DefaultValue)); break; case Yarn.Type.Bool: value = new Operand(Convert.ToBoolean(declaration.DefaultValue)); break; default: throw new ArgumentOutOfRangeException($"Cannot create an initial value for type {declaration.ReturnType}"); } finalResult.Program.InitialValues.Add(declaration.Name, value); } finalResult.Declarations = derivedVariableDeclarations; finalResult.FileTags = fileTags; return(finalResult); }