Пример #1
0
        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);
        }
Пример #2
0
        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);
        }
Пример #3
0
        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);
        }
Пример #4
0
            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);
            }
Пример #5
0
        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");
        }
Пример #6
0
        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);
                }
        }
Пример #7
0
        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
            }));
        }
Пример #8
0
            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);
            }
Пример #9
0
        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, "+");
        }
Пример #10
0
        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);
        }