public void TestFailingFunctionDeclarationReturnType() { dialogue.Library.RegisterFunction("func_invalid_return", () => new List <int> { 1, 2, 3 }); var source = CreateTestNode(@"Hello"); Assert.Throws <TypeException>(() => { Compiler.Compile(CompilationJob.CreateFromString("input", source, dialogue.Library)); }); }
public void TestNullNotAllowed() { var source = CreateTestNode(@" <<declare $err = null>> // error, null not allowed "); var ex = Assert.Throws <TypeException>(() => { Compiler.Compile(CompilationJob.CreateFromString("input", source)); }); Assert.Contains("Null is not a permitted type", ex.Message); }
public void TestImplicitFunctionDeclarations() { var source = CreateTestNode(@" {func_void_bool()} {func_void_bool() and bool(func_void_bool())} { 1 + func_void_int() } { ""he"" + func_void_str() } {func_int_bool(1)} {true and func_int_bool(1)} {func_bool_bool(false)} {true and func_bool_bool(false)} {func_str_bool(""hello"")} {true and func_str_bool(""hello"")} "); dialogue.Library.RegisterFunction("func_void_bool", () => true); dialogue.Library.RegisterFunction("func_void_int", () => 1); dialogue.Library.RegisterFunction("func_void_str", () => "llo"); dialogue.Library.RegisterFunction("func_int_bool", (int i) => true); dialogue.Library.RegisterFunction("func_bool_bool", (bool b) => true); dialogue.Library.RegisterFunction("func_str_bool", (string s) => true); testPlan = new TestPlanBuilder() .AddLine("True") .AddLine("True") .AddLine("2") .AddLine("hello") .AddLine("True") .AddLine("True") .AddLine("True") .AddLine("True") .AddLine("True") .AddLine("True") .GetPlan(); // the library is NOT attached to this compilation job; all // functions will be implicitly declared var compilationJob = CompilationJob.CreateFromString("input", source); var result = Compiler.Compile(compilationJob); Assert.Empty(result.Diagnostics); dialogue.SetProgram(result.Program); stringTable = result.StringTable; RunStandardTestcase(); }
public void TestLineTagsAreAdded() { // Arrange var originalText = @"title: Program --- // A comment. No line tag is added. A single line, with no line tag. A single line, with a line tag. #line:expected_abc123 -> An option, with no line tag. -> An option, with a line tag. #line:expected_def456 A line with no tag, but a comment at the end. // a comment A line with a tag, and a comment. #line:expected_ghi789 // a comment // A comment with no text: // // A comment with a single space: // ==="; // Act var output = Utility.AddTagsToLines(originalText); var compilationJob = CompilationJob.CreateFromString("input", output); compilationJob.CompilationType = CompilationJob.Type.StringsOnly; var compilationResult = Compiler.Compile(compilationJob); // Assert var lineTagRegex = new Regex(@"#line:\w+"); var lineTagAfterComment = new Regex(@"\/\/.*#line:\w+"); // Ensure that the right number of tags in total is present var expectedExistingTags = 3; var expectedNewTags = 3; var expectedTotalTags = expectedExistingTags + expectedNewTags; Assert.Equal(expectedTotalTags, lineTagRegex.Matches(output).Count); // No tags were added after a comment foreach (var line in output.Split('\n')) { Assert.False(lineTagAfterComment.IsMatch(line), $"'{line}' should not contain a tag after a comment"); } var expectedResults = new (string tag, string line)[] {
public void TestExpressionsDisallowMismatchedTypes() { var source = CreateTestNode(@" <<declare $int = 5>> <<set $int = ""5"">> // error, can't assign string to a variable declared int "); var ex = Assert.Throws <TypeException>(() => { Compiler.Compile(CompilationJob.CreateFromString("input", source)); }); Assert.Contains("$int (Number) cannot be assigned a String", ex.Message); }
void TestVariableDeclarationsDisallowDuplicates() { var source = CreateTestNode(@" <<declare $int = 5>> <<declare $int = 6>> // redeclaration "); var ex = Assert.Throws <TypeException>(() => { Compiler.Compile(CompilationJob.CreateFromString("input", source)); }); Assert.Contains("$int has already been declared", ex.Message); }
public void TestMissingNode() { var path = Path.Combine(TestDataPath, "TestCases", "Smileys.yarn"); var result = Compiler.Compile(CompilationJob.CreateFromFiles(path)); Assert.Empty(result.Diagnostics); dialogue.SetProgram(result.Program); runtimeErrorsCauseFailures = false; Assert.Throws <DialogueException>(() => dialogue.SetNode("THIS NODE DOES NOT EXIST")); }
public void TestAnalysis() { ICollection <Yarn.Analysis.Diagnosis> diagnoses; Yarn.Analysis.Context context; // this script has the following variables: // $foo is read from and written to // $bar is written to but never read // this means that there should be one diagnosis result context = new Yarn.Analysis.Context(typeof(Yarn.Analysis.UnusedVariableChecker)); var path = Path.Combine(TestDataPath, "AnalysisTest.yarn"); CompilationJob compilationJob = CompilationJob.CreateFromFiles(path); compilationJob.Library = dialogue.Library; var result = Compiler.Compile(compilationJob); Assert.Empty(result.Diagnostics); stringTable = result.StringTable; dialogue.SetProgram(result.Program); dialogue.Analyse(context); diagnoses = new List <Yarn.Analysis.Diagnosis>(context.FinishAnalysis()); Assert.Equal(1, diagnoses.Count); Assert.Contains("Variable $bar is assigned, but never read from", diagnoses.First().message); dialogue.UnloadAll(); context = new Yarn.Analysis.Context(typeof(Yarn.Analysis.UnusedVariableChecker)); result = Compiler.Compile(CompilationJob.CreateFromFiles(new[] { Path.Combine(SpaceDemoScriptsPath, "Ship.yarn"), Path.Combine(SpaceDemoScriptsPath, "Sally.yarn"), }, dialogue.Library)); Assert.Empty(result.Diagnostics); dialogue.SetProgram(result.Program); dialogue.Analyse(context); diagnoses = new List <Yarn.Analysis.Diagnosis>(context.FinishAnalysis()); // This script should contain no unused variables Assert.Empty(diagnoses); }
public void TestImplicitVariableDeclarations(string value, string typeName) { var source = CreateTestNode($@" <<set $v = {value}>> "); var result = Compiler.Compile(CompilationJob.CreateFromString("<input>", source)); Assert.Empty(result.Diagnostics); var declarations = result.Declarations.Where(d => d.Name == "$v"); Assert.Collection(declarations, d => Assert.Equal(d.Type.Name, typeName)); }
public void TestFunctionArgumentTypeInference() { // Register some functions dialogue.Library.RegisterFunction("ConcatString", (string a, string b) => a + b); dialogue.Library.RegisterFunction("AddInt", (int a, int b) => a + b); dialogue.Library.RegisterFunction("AddFloat", (float a, float b) => a + b); dialogue.Library.RegisterFunction("NegateBool", (bool a) => !a); // Run some code to exercise these functions var source = CreateTestNode(@" <<declare $str = """">> <<declare $int = 0>> <<declare $float = 0.0>> <<declare $bool = false>> <<set $str = ConcatString(""a"", ""b"")>> <<set $int = AddInt(1,2)>> <<set $float = AddFloat(1,2)>> <<set $bool = NegateBool(true)>> "); var result = Compiler.Compile(CompilationJob.CreateFromString("input", source, dialogue.Library)); Assert.Empty(result.Diagnostics); stringTable = result.StringTable; dialogue.SetProgram(result.Program); dialogue.SetNode("Start"); do { dialogue.Continue(); } while (dialogue.IsActive); // The values should be of the right type and value this.storage.TryGetValue <string>("$str", out var strValue); Assert.Equal("ab", strValue); this.storage.TryGetValue <float>("$int", out var intValue); Assert.Equal(3, intValue); this.storage.TryGetValue <float>("$float", out var floatValue); Assert.Equal(3, floatValue); this.storage.TryGetValue <bool>("$bool", out var boolValue); Assert.False(boolValue); }
public void TestFunctionSignatures(string source) { dialogue.Library.RegisterFunction("func_void_bool", () => true); dialogue.Library.RegisterFunction("func_int_bool", (int i) => true); dialogue.Library.RegisterFunction("func_int_int_bool", (int i, int j) => true); dialogue.Library.RegisterFunction("func_string_string_bool", (string i, string j) => true); var correctSource = CreateTestNode($@" <<declare $bool = false>> <<set $bool = func_string_string_bool(""1"", ""2"")>> "); // Should compile with no exceptions Compiler.Compile(CompilationJob.CreateFromString("input", correctSource, dialogue.Library)); }
public void TestExampleScript() { errorsCauseFailures = false; var path = Path.Combine(TestDataPath, "Example.yarn"); var testPath = Path.ChangeExtension(path, ".testplan"); var result = Compiler.Compile(CompilationJob.CreateFromFiles(path)); dialogue.SetProgram(result.Program); stringTable = result.StringTable; this.LoadTestPlan(testPath); RunStandardTestcase(); }
protected override void AutomationProcessRecord() { CompilationJob job = null; if (this.Parameters != null && this.Parameters.Contains("ConfigurationData")) { throw new ArgumentException( string.Format( CultureInfo.CurrentCulture, Resources.ConfigurationDataShouldNotBeInJobParameters, "-ConfigurationData")); } job = this.AutomationClient.StartCompilationJob(this.ResourceGroupName, this.AutomationAccountName, this.ConfigurationName, this.Parameters, this.ConfigurationData); this.WriteObject(job); }
public void TestGettingTags() { var path = Path.Combine(TestDataPath, "Example.yarn"); var result = Compiler.Compile(CompilationJob.CreateFromFiles(path)); dialogue.SetProgram(result.Program); var source = dialogue.GetTagsForNode("LearnMore"); Assert.NotNull(source); Assert.NotEmpty(source); Assert.Equal("rawText", source.First()); }
public void TestEndOfNotesWithOptionsNotAdded() { var path = Path.Combine(TestDataPath, "SkippedOptions.yarn"); var result = Compiler.Compile(CompilationJob.CreateFromFiles(path)); dialogue.SetProgram(result.Program); stringTable = result.StringTable; dialogue.OptionsHandler = delegate(OptionSet optionSets) { Assert.False(true, "Options should not be shown to the user in this test."); }; dialogue.SetNode(); dialogue.Continue(); }
public void TestGettingRawSource() { var path = Path.Combine(TestDataPath, "Example.yarn"); var result = Compiler.Compile(CompilationJob.CreateFromFiles(path)); dialogue.SetProgram(result.Program); stringTable = result.StringTable; var sourceID = dialogue.GetStringIDForNode("LearnMore"); var source = stringTable[sourceID].text; Assert.NotNull(source); Assert.Equal("A: HAHAHA\n", source); }
public void TestVariableDeclarationAnnotations() { var source = CreateTestNode(@" <<declare $int = 42 ""a number"">> <<declare $str = ""Hello"" ""a string"">> <<declare $bool = true ""a bool"">> "); var result = Compiler.Compile(CompilationJob.CreateFromString("input", source, dialogue.Library)); var expectedDeclarations = new List <Declaration>() { new Declaration { Name = "$int", ReturnType = Yarn.Type.Number, DefaultValue = 42f, Description = "a number", }, new Declaration { Name = "$str", ReturnType = Yarn.Type.String, DefaultValue = "Hello", Description = "a string", }, new Declaration { Name = "$bool", ReturnType = Yarn.Type.Bool, DefaultValue = true, Description = "a bool", }, }; var actualDeclarations = new List <Declaration>(result.Declarations); for (int i = 0; i < expectedDeclarations.Count; i++) { Declaration expected = expectedDeclarations[i]; Declaration actual = actualDeclarations[i]; Assert.Equal(expected.Name, actual.Name); Assert.Equal(expected.ReturnType, actual.ReturnType); Assert.Equal(expected.DefaultValue, actual.DefaultValue); Assert.Equal(expected.DeclarationType, actual.DeclarationType); Assert.Equal(expected.Description, actual.Description); } }
public void TestFailingFunctionSignatures(string source, string expectedExceptionMessage) { dialogue.Library.RegisterFunction("func_void_bool", () => true); dialogue.Library.RegisterFunction("func_int_bool", (int i) => true); dialogue.Library.RegisterFunction("func_int_int_bool", (int i, int j) => true); dialogue.Library.RegisterFunction("func_string_string_bool", (string i, string j) => true); var failingSource = CreateTestNode($@" <<declare $bool = false>> <<declare $int = 1>> {source} "); var result = Compiler.Compile(CompilationJob.CreateFromString("input", failingSource, dialogue.Library)); Assert.Collection(result.Diagnostics, p => Assert.Matches(expectedExceptionMessage, p.Message)); }
public void TestNodeHeaders() { var path = Path.Combine(TestDataPath, "Headers.yarn"); var result = Compiler.Compile(CompilationJob.CreateFromFiles(path)); Assert.Equal(4, result.Program.Nodes.Count); foreach (var tag in new[] { "one", "two", "three" }) { Assert.Contains(tag, result.Program.Nodes["Tags"].Tags); } // Assert.Contains("version:2", result.FileTags); Assert.Contains(path, result.FileTags.Keys); Assert.Equal(1, result.FileTags.Count); Assert.Equal(1, result.FileTags[path].Count()); Assert.Contains("file_header", result.FileTags[path]); }
public void TestExtraneousElse() { var source = CreateTestNode(@" <<if true>> One <<else>> Two <<else>> Three <<endif>>"); var result = Compiler.Compile(CompilationJob.CreateFromString("<input>", source)); Assert.Collection(result.Diagnostics, d => Assert.Contains("More than one <<else>> statement in an <<if>> statement isn't allowed", d.Message), d => Assert.Contains("Unexpected \"endif\" while reading a statement", d.Message) ); }
public void TestLoadingNodes() { var path = Path.Combine(TestDataPath, "Projects", "Basic", "Test.yarn"); var result = Compiler.Compile(CompilationJob.CreateFromFiles(path)); dialogue.SetProgram(result.Program); stringTable = result.StringTable; // high-level test: load the file, verify it has the nodes we want, // and run one Assert.Equal(3, dialogue.NodeNames.Count()); Assert.True(dialogue.NodeExists("TestNode")); Assert.True(dialogue.NodeExists("AnotherTestNode")); Assert.True(dialogue.NodeExists("ThirdNode")); }
public void TestTypeConversionFailure(string test) { var source = CreateTestNode(test); testPlan = new TestPlanBuilder() .AddLine("test failure if seen") .GetPlan(); Assert.Throws <FormatException>(() => { var compilationJob = CompilationJob.CreateFromString("input", source, dialogue.Library); var result = Compiler.Compile(compilationJob); dialogue.SetProgram(result.Program); stringTable = result.StringTable; RunStandardTestcase(); }); }
public void TestIfStatementExpressionsMustBeBoolean() { var source = CreateTestNode(@" <<declare $str = ""hello"" as string>> <<declare $bool = true>> <<if $bool>> // ok Hello <<endif>> <<if $str>> // error, must be a bool Hello <<endif>> "); var result = Compiler.Compile(CompilationJob.CreateFromString("input", source)); Assert.Collection(result.Diagnostics, p => Assert.Contains("Terms of 'if statement' must be Bool, not String", p.Message)); }
public void TestExplicitTypes() { var source = CreateTestNode(@" <<declare $str = ""hello"" as string>> <<declare $int = 1 as number>> <<declare $bool = false as bool>> "); var result = Compiler.Compile(CompilationJob.CreateFromString("input", source, dialogue.Library)); Assert.Empty(result.Diagnostics); Assert.Collection( result.Declarations.Where(d => d.Name.StartsWith("$")), d => { Assert.Equal(d.Name, "$str"); Assert.Equal(d.Type.Name, "String"); }, d => { Assert.Equal(d.Name, "$int"); Assert.Equal(d.Type.Name, "Number"); }, d => { Assert.Equal(d.Name, "$bool"); Assert.Equal(d.Type.Name, "Bool"); } ); }
public void TestExpressionsRequireCompatibleTypes(bool declare) { var source = CreateTestNode($@" {(declare ? "<<declare $int = 0>>" : "")} {(declare ? "<<declare $bool = false>>" : "")} {(declare ? "<<declare $str = \"\">>" : "")} <<set $int = 1>> <<set $int = 1 + 1>> <<set $int = 1 - 1>> <<set $int = 1 * 2>> <<set $int = 1 / 2>> <<set $int = 1 % 2>> <<set $int += 1>> <<set $int -= 1>> <<set $int *= 1>> <<set $int /= 1>> <<set $int %= 1>> <<set $str = ""hello"">> <<set $str = ""hel"" + ""lo"">> <<set $bool = true>> <<set $bool = 1 > 1>> <<set $bool = 1 < 1>> <<set $bool = 1 <= 1>> <<set $bool = 1 >= 1>> <<set $bool = ""hello"" == ""hello"">> <<set $bool = ""hello"" != ""goodbye"">> <<set $bool = 1 == 1>> <<set $bool = 1 != 2>> <<set $bool = true == true>> <<set $bool = true != false>> <<set $bool = (1 + 1) > 2>> "); // Should compile with no exceptions var result = Compiler.Compile(CompilationJob.CreateFromString("input", source)); Assert.Empty(result.Diagnostics); }
public void TestNodeExists() { var path = Path.Combine(SpaceDemoScriptsPath, "Sally.yarn"); CompilationJob compilationJob = CompilationJob.CreateFromFiles(path); compilationJob.Library = dialogue.Library; var result = Compiler.Compile(compilationJob); dialogue.SetProgram(result.Program); Assert.True(dialogue.NodeExists("Sally")); // Test clearing everything dialogue.UnloadAll(); Assert.False(dialogue.NodeExists("Sally")); }
public void TestOperatorsAreTypeChecked([CombinatorialValues( "= 1 + 1", "= 1 / 1", "= 1 - 1", "= 1 * 1", "= 1 % 1", "+= 1", "-= 1", "/= 1", "*= 1" )] string operation, bool declared) { string source = CreateTestNode($@" {(declared ? "<<declare $var = 0>>" : "")} <<set $var {operation}>> "); Compiler.Compile(CompilationJob.CreateFromString("input", source, dialogue.Library)); }
private void ImportYarn(AssetImportContext ctx) { var sourceText = File.ReadAllText(ctx.assetPath); string fileName = System.IO.Path.GetFileNameWithoutExtension(ctx.assetPath); var text = new TextAsset(File.ReadAllText(ctx.assetPath)); // Add this container to the imported asset; it will be what // the user interacts with in Unity ctx.AddObjectToAsset("Program", text, YarnEditorUtility.GetYarnDocumentIconTexture()); ctx.SetMainObject(text); Yarn.Program compiledProgram = null; IDictionary <string, Yarn.Compiler.StringInfo> stringTable = null; parseErrorMessage = null; try { // Compile the source code into a compiled Yarn program (or // generate a parse error) var compilationJob = CompilationJob.CreateFromString(fileName, sourceText, null); compilationJob.CompilationType = CompilationJob.Type.StringsOnly; var result = Yarn.Compiler.Compiler.Compile(compilationJob); LastImportHadImplicitStringIDs = result.ContainsImplicitStringTags; LastImportHadAnyStrings = result.StringTable.Count > 0; stringTable = result.StringTable; compiledProgram = result.Program; isSuccessfullyParsed = true; parseErrorMessage = string.Empty; } catch (Yarn.Compiler.ParseException e) { isSuccessfullyParsed = false; parseErrorMessage = e.Message; ctx.LogImportError($"Error importing {ctx.assetPath}: {e.Message}"); return; } }
/// <summary> /// Generates a collection of <see cref="StringTableEntry"/> /// objects, one for each line in this Yarn Project's scripts. /// </summary> /// <returns>An IEnumerable containing a <see /// cref="StringTableEntry"/> for each of the lines in the Yarn /// Project, or <see langword="null"/> if the Yarn Project contains /// errors.</returns> internal IEnumerable <StringTableEntry> GenerateStringsTable() { var pathsToImporters = sourceScripts.Where(s => s != null).Select(s => AssetDatabase.GetAssetPath(s)); if (pathsToImporters.Count() == 0) { // We have no scripts to work with - return an empty // collection - there's no error, but there's no content // either return(new List <StringTableEntry>()); } // We now now compile! var job = CompilationJob.CreateFromFiles(pathsToImporters); job.CompilationType = CompilationJob.Type.StringsOnly; CompilationResult compilationResult; try { compilationResult = Compiler.Compiler.Compile(job); } catch (ParseException) { Debug.LogError($"Can't generate a strings table from a Yarn Project that contains compile errors", null); return(null); } IEnumerable <StringTableEntry> stringTableEntries = compilationResult.StringTable.Select(x => new StringTableEntry { ID = x.Key, Language = defaultLanguage, Text = x.Value.text, File = x.Value.fileName, Node = x.Value.nodeName, LineNumber = x.Value.lineNumber.ToString(), Lock = YarnImporter.GetHashString(x.Value.text, 8), }); return(stringTableEntries); }
public void TestMergingNodes() { var sallyPath = Path.Combine(SpaceDemoScriptsPath, "Sally.yarn"); var shipPath = Path.Combine(SpaceDemoScriptsPath, "Ship.yarn"); CompilationJob compilationJobSally = CompilationJob.CreateFromFiles(sallyPath); CompilationJob compilationJobSallyAndShip = CompilationJob.CreateFromFiles(sallyPath, shipPath); compilationJobSally.Library = dialogue.Library; compilationJobSallyAndShip.Library = dialogue.Library; var resultSally = Compiler.Compile(compilationJobSally); var resultSallyAndShip = Compiler.Compile(compilationJobSallyAndShip); // Loading code with the same contents should throw Assert.Throws <InvalidOperationException>(delegate() { var combinedNotWorking = Program.Combine(resultSally.Program, resultSallyAndShip.Program); }); }
private void RebuildDependencyInfo(CompilationJob job, IDictionary<CompilationJob, IList<CompilationJob>> cache, IList<CompilationJob> stackCheck) { if (stackCheck.Contains(job)) { System.Diagnostics.Trace.TraceWarning("Potential recursive Dependencies in {0} and {1}", job.NameSpace, stackCheck.Last().NameSpace); return; } stackCheck.Add(job); // Check if we have this job in the cache if (cache.ContainsKey(job)) { foreach (CompilationJob dependentJob in cache[job]) { this.jobs[dependentJob]++; this.RebuildDependencyInfo(dependentJob, cache, stackCheck); } stackCheck.Remove(job); return; } // If not load the cache info and re-start the rebuild cache.Add(job, new List<CompilationJob>()); TempFileLight file = JsonExtensions.LoadFromFile<TempFileLight>(job.SourceFile, job.IsCompressed); if (file.Includes != null) { foreach (string include in file.Includes) { if (this.includeJobDictionary.ContainsKey(include)) { CompilationJob dependentJob = this.includeJobDictionary[include]; cache[job].Add(dependentJob); } } } stackCheck.Remove(job); this.RebuildDependencyInfo(job, cache, stackCheck); }
private CompilationJob CreateJob(TempProject project, TempProjectFileEntry entry) { var job = new CompilationJob { IsCompressed = entry.IsCompressed, NameSpace = this.compilerState.BaseNameSpace }; if (entry.RelativePath != null && !entry.RelativePath.IsNull) { string fileNameSpace = this.compiler.GetNameSpace(entry.RelativePath); job.NameSpace = string.Join(Compiler.NameSpaceSeparator.ToString(CultureInfo.InvariantCulture), job.NameSpace, fileNameSpace); } job.NameSpace = this.GetNameSpace(project.RootNameSpace, entry); CarbonDirectory fileTargetDirectory = this.targetDirectory.ToDirectory(SourceTargetDir); if (entry.RelativePath != null) { fileTargetDirectory = fileTargetDirectory.ToDirectory(entry.RelativePath); } job.SourceFile = this.targetDirectory.ToFile(entry.File); job.TargetFile = fileTargetDirectory.ToFile(entry.RelativeFile); if (entry.Classes != null) { foreach (string @class in entry.Classes) { var classNameSpace = string.Concat(job.NameSpace, Compiler.NameSpaceSeparator, @class); if (this.includeJobDictionary.ContainsKey(classNameSpace)) { System.Diagnostics.Trace.TraceWarning("Multiple class definitions for " + classNameSpace); continue; } this.includeJobDictionary.Add(classNameSpace, job); } } return job; }