protected override MessageHolder Test(Mode mode, int errorsExpected, LNodePrinterOptions printerOptions, string text, params LNode[] expected) { var messages = new MessageHolder(); var les3 = Les3LanguageService.Value; var results = les3.Parse(text, messages, mode == Mode.Expr ? ParsingMode.Expressions : ParsingMode.Statements, true).ToList(); if (messages.List.Count != System.Math.Max(errorsExpected, 0)) { messages.WriteListTo(ConsoleMessageSink.Value); int errorCount = messages.List.Count(msg => msg.Severity >= Severity.Error); AreEqual(errorsExpected, errorCount, "Error count was {0} for «{1}»", errorCount, text); // fail } for (int i = 0; i < expected.Length; i++) { LNode expect = expected[i], actual = results.TryGet(i, null); if (!expect.Equals(actual, LNode.CompareMode.TypeMarkers)) { var options = new Les3PrinterOptions { PrintTriviaExplicitly = true, IndentString = " " }; AreEqual(les3.Print(expect, null, null, options), les3.Print(actual, null, null, options)); AreEqual(expect, actual); Fail("{0} has a different type marker than {1}", expect, actual); } } AreEqual(expected.Length, results.Count, "Got more result nodes than expected"); return(messages); }
protected override MessageHolder Test(Mode mode, int parseErrors, LNodePrinterOptions options, string expected, params LNode[] inputs) { var messages = new MessageHolder(); options = options ?? new Les3PrinterOptions { IndentString = " " }; if (parseErrors == 0) { if (mode == Mode.Exact) { var result = Les3LanguageService.Value.Print(inputs, messages, ParsingMode.Statements, options); Assert.AreEqual(expected, result); } else { // Start by parsing. If parsing fails, just stop; such errors are // already reported by LesParserTests so we need not report them here. var _ = Les3LanguageService.Value.Parse(expected, msgs: messages); if (messages.List.All(msg => msg.Severity < Severity.Error)) { foreach (LNode input in inputs) { DoPrinterTest(input, mode, options); } } } } return(messages); }
protected override void WriteOutput(InputOutput io) { Results = io.Output; Output = new StringBuilder(); var opts = new LNodePrinterOptions { IndentString = IndentString, NewlineString = NewlineString }; LNode.Printer.Print(Results, Output, Sink, null, opts); }
protected override void WriteOutput(InputOutput io) { VList <LNode> results = io.Output; Output.AppendFormat("// Generated from {1} by LeMP {2}.{0}", NewlineString, io.FileName, typeof(Compiler).Assembly.GetName().Version.ToString()); var opts = new LNodePrinterOptions { IndentString = IndentString, NewlineString = NewlineString }; io.OutPrinter.Print(results, Output, Sink, ParsingMode.File, opts); }
public void SaveRangeIsCalled() { var ranges = new List <Triplet <ILNode, IndexRange, int> >(); var options = new LNodePrinterOptions { SaveRange = (n, r, d) => ranges.Add(Triplet.Create(n, r, d)) }; LNode node = F.Call(_("var"), F.Call(S.Assign, x, F.Call(S.Sub, two))); Stmt("var(x = -2);", node); string output = Les2LanguageService.Value.Print(node, null, ParsingMode.Statements, options); ExpectSavedRange(ranges, output, node, "var(x = -2);"); // "(" is part of the target because if there is %trailing trivia on the target, it appears after "(" ExpectSavedRange(ranges, output, node.Target, "var("); ExpectSavedRange(ranges, output, node[0], "x = -2"); ExpectSavedRange(ranges, output, node[0][0], "x"); ExpectSavedRange(ranges, output, node[0][1], "-2"); ExpectSavedRange(ranges, output, node[0][1].Target, "-"); ExpectSavedRange(ranges, output, node[0][1][0], "2"); // The space is included because suffix trivia on an operator Target is printed after the space ExpectSavedRange(ranges, output, node[0].Target, "= "); ranges.Clear(); LNode body, signature; node = F.Call(S.Lambda, signature = F.Call("MyMethod", F.Call(S.Colon, x, F.Call(S.Array, _("int")))), F.Braces(body = F.Call(Foo, F.Call("'.+", x, F.Literal(123)), F.Tuple(a, b)))); // There's extra indent inside the braces because the braces are a subexpression of `=>` Exact("MyMethod(x : [int]) => {\n Foo(x .+ 123, (a; b));\n };", node); output = Les2LanguageService.Value.Print(node, null, ParsingMode.Statements, options); ExpectSavedRange(ranges, output, node.Target, "=> "); ExpectSavedRange(ranges, output, signature, "MyMethod(x : [int])"); ExpectSavedRange(ranges, output, signature.Target, "MyMethod("); ExpectSavedRange(ranges, output, body, "Foo(x .+ 123, (a; b));"); ExpectSavedRange(ranges, output, body.Target, "Foo("); ExpectSavedRange(ranges, output, signature[0], "x : [int]"); ExpectSavedRange(ranges, output, signature[0].Target, ": "); ExpectSavedRange(ranges, output, signature[0][1], "[int]"); ExpectSavedRange(ranges, output, signature[0][1][0], "int"); // It could be argued that the comma shouldn't be included, but it allows suffix trivia to appear after the comma ExpectSavedRange(ranges, output, body[0], "x .+ 123, "); ExpectSavedRange(ranges, output, body[0][0], "x"); ExpectSavedRange(ranges, output, body[0][1], "123"); ExpectSavedRange(ranges, output, body[0].Target, ".+ "); ExpectSavedRange(ranges, output, body[1], "(a; b)"); ExpectSavedRange(ranges, output, body[1][0], "a; "); ExpectSavedRange(ranges, output, body[1][1], "b"); }
protected virtual void WriteOutput(InputOutput io) { Debug.Assert(io.FileName != io.OutFileName); Sink.Write(Severity.Verbose, io, "Writing output file: {0}", io.OutFileName); using (var stream = File.Open(io.OutFileName, FileMode.Create, FileAccess.Write, FileShare.Read)) using (var writer = new StreamWriter(stream, Encoding.UTF8)) { var options = new LNodePrinterOptions { IndentString = IndentString, NewlineString = NewlineString }; var str = io.OutPrinter.Print(io.Output, Sink, null, options); writer.Write(str); } }
private void DoPrinterTest(LNode input, Mode mode, LNodePrinterOptions options) { var messages = new MessageHolder(); var printed = Les3LanguageService.Value.Print(input, messages, mode == Mode.Expr ? ParsingMode.Expressions : null, options); Assert.AreEqual(0, messages.List.Count); var reparsed = Les3LanguageService.Value.Parse(printed, msgs: messages); if (messages.List.Count != 0) { Assert.Fail("Printed node «{0}» causes error on parsing: {1}", printed, messages.List[0].Formatted); } Assert.AreEqual(1, reparsed.Count); Assert.AreEqual(input, reparsed[0], "Printed node «{0}» is different from original node.\n Original: «{1}»\n Reparsed: «{2}»", printed, LNode.Printer.Print(input, null, null, new LNodePrinterOptions { PrintTriviaExplicitly = true }), LNode.Printer.Print(reparsed[0], null, null, new LNodePrinterOptions { PrintTriviaExplicitly = true })); }
protected override void WriteOutput(InputOutput io) { VList <LNode> results = io.Output; if (!NoOutHeader) { Output.AppendFormat( "// Generated from {1} by LeMP custom tool. LeMP version: {2}{0}" + "// Note: you can give command-line arguments to the tool via 'Custom Tool Namespace':{0}" + "// --no-out-header Suppress this message{0}" + "// --verbose Allow verbose messages (shown by VS as 'warnings'){0}" + "// --timeout=X Abort processing thread after X seconds (default: 10){0}" + "// --macros=FileName.dll Load macros from FileName.dll, path relative to this file {0}" + "// Use #importMacros to use macros in a given namespace, e.g. #importMacros(Loyc.LLPG);{0}", NewlineString, Path.GetFileName(io.FileName), typeof(MacroProcessor).Assembly.GetName().Version.ToString()); } var options = new LNodePrinterOptions { IndentString = IndentString, NewlineString = NewlineString }; LNode.Printer.Print(results, Output, Sink, ParsingMode.File, options); }
public void SaveRangeIsCalled() { var ranges = new List <Triplet <ILNode, IndexRange, int> >(); var options = new LNodePrinterOptions { SaveRange = (n, r, d) => ranges.Add(Triplet.Create(n, r, d)) }; LNode node = F.Var(F.Int32, F.Call(S.Assign, x, two)); Stmt("int x = 2;", node); string output = EcsLanguageService.Value.Print(node, null, ParsingMode.Statements, options); ExpectSavedRange(ranges, output, node, "int x = 2;"); ExpectSavedRange(ranges, output, node[0], "int"); ExpectSavedRange(ranges, output, node[1], "x = 2"); ExpectSavedRange(ranges, output, node[1][0], "x"); ExpectSavedRange(ranges, output, node[1][1], "2"); ExpectSavedRange(ranges, output, node[1].Target, "="); ranges.Clear(); LNode body; node = F.Fn(F.Void, _("MyMethod"), F.AltList(), body = F.Call(Foo, F.Call(S.Add, x, one))); Stmt("void MyMethod() => Foo(x + 1);", node); output = EcsLanguageService.Value.Print(node, null, ParsingMode.Statements, options); ExpectSavedRange(ranges, output, node, "void MyMethod() => Foo(x + 1);"); ExpectSavedRange(ranges, output, node[0], "void"); ExpectSavedRange(ranges, output, node[1], "MyMethod"); ExpectSavedRange(ranges, output, node[2], "()"); ExpectSavedRange(ranges, output, body, "Foo(x + 1)"); ExpectSavedRange(ranges, output, body.Target, "Foo"); ExpectSavedRange(ranges, output, body[0], "x + 1"); ExpectSavedRange(ranges, output, body[0][0], "x"); ExpectSavedRange(ranges, output, body[0][1], "1"); ExpectSavedRange(ranges, output, body[0].Target, "+"); }
private static object RunCSharpCodeWithRoslyn(LNode parent, LNodeList code, IMacroContext context, ParsingMode printMode = null) { // Note: when using compileTimeAndRuntime, the transforms here affect the version // sent to Roslyn, but not the runtime version placed in the output file. code = code.SmartSelectMany(stmt => { // Ensure #r gets an absolute path; I don't know what Roslyn does with a // relative path (maybe WithMetadataResolver would let me control this, // but it's easier not to) if ((stmt.Calls(S.CsiReference, 1) || stmt.Calls(S.CsiLoad, 1)) && stmt[0].Value is string fileName) { fileName = fileName.Trim().WithoutPrefix("\"").WithoutSuffix("\""); var inputFolder = context.ScopedProperties.TryGetValue((Symbol)"#inputFolder", "").ToString(); var fullPath = Path.Combine(inputFolder, fileName); return(LNode.List(stmt.WithArgChanged(0, stmt[0].WithValue("\"" + fullPath + "\"")))); } // For each (top-level) LexicalMacro method, call #macro_context.RegisterMacro(). LNode attribute = null; if ((attribute = stmt.Attrs.FirstOrDefault( attr => AppearsToCall(attr, "LeMP", nameof(LexicalMacroAttribute).WithoutSuffix("Attribute")) || AppearsToCall(attr, "LeMP", nameof(LexicalMacroAttribute)))) != null && EcsValidators.MethodDefinitionKind(stmt, out _, out var macroName, out _, out _) == S.Fn) { var setters = SeparateAttributeSetters(ref attribute); attribute = attribute.WithTarget((Symbol)nameof(LexicalMacroAttribute)); setters.Insert(0, attribute); var newAttribute = F.Call(S.New, setters); var registrationCommand = F.Call(F.Dot(__macro_context, nameof(IMacroContext.RegisterMacro)), F.Call(S.New, F.Call(nameof(MacroInfo), F.Null, newAttribute, macroName))); return(LNode.List(stmt, registrationCommand)); } return(LNode.List(stmt)); }); var outputLocationMapper = new LNodeRangeMapper(); var options = new LNodePrinterOptions { IndentString = " ", SaveRange = outputLocationMapper.SaveRange }; string codeText = EcsLanguageService.WithPlainCSharpPrinter.Print(code, context.Sink, printMode, options); _roslynSessionLog?.WriteLine(codeText); _roslynSessionLog?.Flush(); _roslynScriptState.GetVariable(__macro_context_sanitized).Value = context; try { // Allow users to write messages via MessageSink.Default using (MessageSink.SetDefault(new MessageSinkFromDelegate((sev, ctx, msg, args) => { _roslynSessionLog?.Write("{0} from user ({1}): ", sev, MessageSink.GetLocationString(ctx)); _roslynSessionLog?.WriteLine(msg, args); context.Sink.Write(sev, ctx, msg, args); }))) { _roslynScriptState = _roslynScriptState.ContinueWithAsync(codeText).Result; } return(_roslynScriptState.ReturnValue); } catch (CompilationErrorException e) when(e.Diagnostics.Length > 0 && e.Diagnostics[0].Location.IsInSource) { // Determine the best location in the source code at which to report the error. // Keep in mind that the error may have occurred in a synthetic location not // found in the original code, and we cannot report such a location. Microsoft.CodeAnalysis.Text.TextSpan range = e.Diagnostics[0].Location.SourceSpan; var errorLocation = new IndexRange(range.Start, range.Length); var mappedErrorLocation = outputLocationMapper.FindRelatedNodes(errorLocation, 10) .FirstOrDefault(p => !p.A.Range.Source.Text.IsEmpty); string locationCaveat = ""; if (mappedErrorLocation.A != null) { bool mappedIsEarly = mappedErrorLocation.B.EndIndex <= errorLocation.StartIndex; if (mappedIsEarly || mappedErrorLocation.B.StartIndex >= errorLocation.EndIndex) { locationCaveat = "; " + "The error occurred at a location ({0}) that doesn't seem to exist in the original code.".Localized( mappedIsEarly ? "after the location indicated".Localized() : "before the location indicated".Localized()); } } // Extract the line where the error occurred, for inclusion in the error message int column = e.Diagnostics[0].Location.GetLineSpan().StartLinePosition.Character; int lineStart = range.Start - column; int lineEnd = codeText.IndexOf('\n', lineStart); if (lineEnd < lineStart) { lineEnd = codeText.Length; } string line = codeText.Substring(lineStart, lineEnd - lineStart); string errorMsg = e.Message + " - in «{0}»{1}".Localized(line, locationCaveat); context.Sink.Error(mappedErrorLocation.A ?? parent, errorMsg); LogRoslynError(e, context.Sink, parent, compiling: true); } catch (Exception e) { while (e is AggregateException ae && ae.InnerExceptions.Count == 1) { e = ae.InnerExceptions[0]; } context.Sink.Error(parent, "An exception was thrown from your code:".Localized() + " " + e.ExceptionMessageAndType()); LogRoslynError(e, context.Sink, parent, compiling: false); } return(NoValue.Value); }