File: DocumentCompiler.cs
Web Access
Project: ILAssembler.csproj (ILAssembler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Antlr4.Runtime;

namespace ILAssembler;
public sealed class DocumentCompiler
{
    public (ImmutableArray<Diagnostic>, CompilationResult?) Compile(SourceText document, Func<string, SourceText> includedDocumentLoader, Func<string, byte[]> resourceLocator, Options options)
    {
        return Compile([document], includedDocumentLoader, resourceLocator, options);
    }

    public (ImmutableArray<Diagnostic>, CompilationResult?) Compile(ImmutableArray<SourceText> documents, Func<string, SourceText> includedDocumentLoader, Func<string, byte[]> resourceLocator, Options options)
    {
        Dictionary<string, SourceText> loadedDocuments = new();
        ImmutableArray<Diagnostic>.Builder diagnostics = ImmutableArray.CreateBuilder<Diagnostic>();

        GrammarVisitor? visitor = null;
        IReadOnlyDictionary<string, string?>? definedVariables = null;

        foreach (var document in documents)
        {
            loadedDocuments[document.Path!] = document;

            var inputSource = new AntlrInputStream(document.Text)
            {
                name = document.Path
            };
            CILLexer lexer = new(inputSource);
            PreprocessedTokenSource preprocessor = new(lexer, path =>
            {
                var includedDocument = includedDocumentLoader(path);
                var includedSource = new AntlrInputStream(includedDocument.Text)
                {
                    name = includedDocument.Path
                };
                loadedDocuments[includedDocument.Path!] = includedDocument;
                return new CILLexer(includedSource);
            }, text => new CILLexer(new AntlrInputStream(text)), definedVariables);

            preprocessor.OnPreprocessorSyntaxError += (source, start, length, msg) =>
            {
                if (loadedDocuments.TryGetValue(source, out var sourceText))
                {
                    diagnostics.Add(new Diagnostic("Preprocessor", DiagnosticSeverity.Error, msg, new Location(new(start, length), sourceText)));
                }
                else
                {
                    diagnostics.Add(new Diagnostic("Preprocessor", DiagnosticSeverity.Error, msg, new Location(new(start, length), new SourceText("", source))));
                }
            };

            CILParser parser = new(new CommonTokenStream(preprocessor));
            parser.RemoveErrorListeners();
            var parserDiagnostics = ImmutableArray.CreateBuilder<Diagnostic>();
            parser.AddErrorListener(new ParserErrorListener(parserDiagnostics, loadedDocuments));
            var result = parser.decls();

            // Add parser diagnostics to the main list
            diagnostics.AddRange(parserDiagnostics);

            visitor ??= new GrammarVisitor(loadedDocuments, options, resourceLocator);

            _ = result.Accept(visitor);

            // Transfer defined constants to the next document
            definedVariables = preprocessor.DefinedVariables;
        }

        if (visitor is null)
        {
            return (diagnostics.ToImmutable(), null);
        }

        var image = visitor.BuildImage();

        bool anyErrors = diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);
        anyErrors |= image.Diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);

        diagnostics.AddRange(image.Diagnostics);

        // In error-tolerant mode, return image even with errors
        bool returnImage = !anyErrors || options.ErrorTolerant;
        return (diagnostics.ToImmutable(), returnImage ? image.Image : null);
    }
}

internal sealed class ParserErrorListener : Antlr4.Runtime.IAntlrErrorListener<IToken>
{
    private readonly ImmutableArray<Diagnostic>.Builder _diagnostics;
    private readonly Dictionary<string, SourceText> _loadedDocuments;

    public ParserErrorListener(ImmutableArray<Diagnostic>.Builder diagnostics, Dictionary<string, SourceText> loadedDocuments)
    {
        _diagnostics = diagnostics;
        _loadedDocuments = loadedDocuments;
    }

    public void SyntaxError(TextWriter output, IRecognizer recognizer, IToken offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e)
    {
        var sourceName = offendingSymbol?.TokenSource?.SourceName ?? "";
        var span = new SourceSpan(offendingSymbol?.StartIndex ?? 0, offendingSymbol is null ? 0 : offendingSymbol.StopIndex - offendingSymbol.StartIndex + 1);
        if (_loadedDocuments.TryGetValue(sourceName, out var sourceText))
        {
            _diagnostics.Add(new Diagnostic("Parser", DiagnosticSeverity.Error, $"line {line}:{charPositionInLine} {msg}", new Location(span, sourceText)));
        }
        else
        {
            _diagnostics.Add(new Diagnostic("Parser", DiagnosticSeverity.Error, $"line {line}:{charPositionInLine} {msg}", new Location(span, new SourceText("", sourceName))));
        }
    }
}