Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added multifile support #182

Merged
merged 13 commits into from
Apr 17, 2024
23 changes: 23 additions & 0 deletions SocietalConstructionTool/Compiler/CompilerError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public class CompilerError
public string Message { get; }
public int? Line { get; }
public int? Column { get; }
public string? Filename { get; set; }

public CompilerError(string message)
{
Expand All @@ -24,8 +25,30 @@ public CompilerError(string message, int line, int column)
Column = column;
}

public CompilerError(string message, string fileName, int line, int column)
{
Message = message;
Filename = fileName;
Line = line;
Column = column;
}

public override string ToString()
{

if (Filename is not null)
{
if (Line is null)
{
return $"{Filename}: {Message}";
}
if (Column is null)
{
return $"{Filename}, Line {Line}: {Message}";
}
return $"{Filename}, Line {Line}, Column {Column}: {Message}";
}

if (Line is null)
{
return Message;
Expand Down
15 changes: 2 additions & 13 deletions SocietalConstructionTool/Compiler/Typechecker/SctTableVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,19 @@

namespace Sct.Compiler.Typechecker
{
public class SctTableVisitor : SctBaseVisitor<SctType>, IErrorReporter
public class SctTableVisitor(CTableBuilder cTableBuilder) : SctBaseVisitor<SctType>, IErrorReporter
{
private CTable? InternalCTable { get; set; }
public CTable CTable => InternalCTable ?? _ctableBuilder.BuildCtable();

private readonly List<CompilerError> _errors = new();
public IEnumerable<CompilerError> Errors => _errors;
private readonly CTableBuilder _ctableBuilder = new();
private readonly CTableBuilder _ctableBuilder = cTableBuilder;

public override SctType VisitStart([NotNull] SctParser.StartContext context)
{
_ = base.VisitStart(context);

InternalCTable = _ctableBuilder.BuildCtable();

var setupType = InternalCTable.GlobalClass.LookupFunctionType("Setup");
if (setupType is null)
{
_errors.Add(new CompilerError("No setup function found"));
}
else if (setupType.ReturnType != TypeTable.Void || setupType.ParameterTypes.Count != 0)
{
_errors.Add(new CompilerError("Setup function must return void and take no arguments"));
}
return TypeTable.None;
}

Expand Down
137 changes: 110 additions & 27 deletions SocietalConstructionTool/SctRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,116 @@ public static class SctRunner
* <summary>
* Reads an SCT source file, statically chekcs it and translates it into C# code
* </summary>
* <param name="filename">The path of the SCT source file</param>
* <param name="filenames">The path of the SCT source file</param>
* <returns>The resulting C# source, or null if compilation failed</returns>
*/
public static (string? outputText, IEnumerable<CompilerError> errors) CompileSct(string filename)
public static (string? outputText, IEnumerable<CompilerError> errors) CompileSct(string[] filenames)
{
// TODO: Add error handling
string input = File.ReadAllText(filename);
ICharStream stream = CharStreams.fromString(input);
ITokenSource lexer = new SctLexer(stream);
ITokenStream tokens = new CommonTokenStream(lexer);
SctParser parser = new(tokens);
var startNode = parser.start();

KeywordContextCheckVisitor keywordChecker = new();
var errors = startNode.Accept(keywordChecker).ToList();
// Make SctTableVisitor take a CTableBuilder as a parameter
// Analyse each file separately
// Add file name to each found error.
// Call CTabelBuilder.BuildCtable() after all files have been visited
// Run the translator on all files concatenated.

// Create a CTableBuilder that is used for all files.
CTableBuilder cTableBuilder = new();
var errors = new List<CompilerError>();

// Run visitor that populates the tables.
var sctTableVisitor = new SctTableVisitor();
_ = startNode.Accept(sctTableVisitor);
var ctable = sctTableVisitor.CTable;
errors.AddRange(sctTableVisitor.Errors);
// Store parses for each file to avoid having to recreate them for type checking.
Dictionary<string, SctParser.StartContext> startNodes = new();

// Run visitor that checks the types.
var sctTypeChecker = new SctTypeChecker(ctable);
_ = startNode.Accept(sctTypeChecker);
parser.Reset();
// Run static analysis on each file separately.
foreach (var file in filenames)
{
string input = File.ReadAllText(file);
ICharStream fileStream = CharStreams.fromString(input);
ITokenSource fileLexer = new SctLexer(fileStream);
ITokenStream fileTokens = new CommonTokenStream(fileLexer);

errors.AddRange(sctTypeChecker.Errors);
var fileParser = new SctParser(fileTokens);
// Save parser for later use.
startNodes[file] = fileParser.start();
var startNode = startNodes[file];

var translator = new SctTranslator();
parser.AddParseListener(translator);
_ = parser.start();
KeywordContextCheckVisitor keywordChecker = new();

// Annotate each error with the filename.
var keywordErrors = startNode.Accept(keywordChecker).ToList();
foreach (var error in keywordErrors)
{
error.Filename = file;
}
errors.AddRange(keywordErrors);

SctReturnCheckVisitor returnChecker = new();
_ = startNode.Accept(returnChecker);
foreach (var error in returnChecker.Errors)
{
error.Filename = file;
}
errors.AddRange(returnChecker.Errors);

// Run visitor that populates the tables using the CTableBuilder.
var sctTableVisitor = new SctTableVisitor(cTableBuilder);
_ = startNode.Accept(sctTableVisitor);

foreach (var error in sctTableVisitor.Errors)
{
error.Filename = file;
}

errors.AddRange(sctTableVisitor.Errors);
}

// Build the CTable after all files have been visited.
// The CTable is used for type checking.
CTable cTable = cTableBuilder.BuildCtable();

var setupType = cTable.GlobalClass.LookupFunctionType("Setup");
if (setupType is null)
{
errors.Add(new CompilerError("No setup function found"));
}
else if (setupType.ReturnType != TypeTable.Void || setupType.ParameterTypes.Count != 0)
{
errors.Add(new CompilerError("Setup function must return void and take no arguments"));
}

// Typecheck each file separately.
// Identifiers from other files are known because the CTable is built from all files.
foreach (var file in filenames)
{
var startNode = startNodes[file];

// Run visitor that checks the types.
var sctTypeChecker = new SctTypeChecker(cTable);
_ = startNode.Accept(sctTypeChecker);

foreach (var error in sctTypeChecker.Errors)
{
error.Filename = file;
}

errors.AddRange(sctTypeChecker.Errors);
}

if (errors.Count > 0)
{
return (null, errors);
}

// Concatenate all files into one string and run the translator on it.
string fullInput = ConcatenateFiles(filenames);
ICharStream stream = CharStreams.fromString(fullInput);
ITokenSource lexer = new SctLexer(stream);
ITokenStream tokens = new CommonTokenStream(lexer);
SctParser parser = new(tokens);

var translator = new SctTranslator();
parser.AddParseListener(translator);
_ = parser.start();

if (translator.Root is null)
{
throw new InvalidOperationException("Translation failed");
Expand Down Expand Up @@ -144,10 +215,10 @@ private static void Run(Assembly assembly, IRuntimeContext initialContext)
*/
public static void CompileAndRun(string[] filenames, IOutputLogger logger)
{
// TODO: Actually concatenate the files. Isak is working on this.
var filename = filenames[0];
var (outputText, errors) = CompileSct(filename);

var (outputText, errors) = CompileSct(filenames);

// TODO: Handle errors from ANTLR. They are not currently being passed to the errors list.
if (errors.Any() || outputText is null)
{
Console.Error.WriteLine("Compilation failed:");
Expand All @@ -169,5 +240,17 @@ public static void CompileAndRun(string[] filenames, IOutputLogger logger)

Run(assembly, logger);
}

private static string ConcatenateFiles(string[] filenames)
{

string result = string.Empty;
foreach (var file in filenames)
{
result += File.ReadAllText(file);
}

return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
namespace SctGenerated
{
using Sct.Runtime;
using System;
using System.Collections.Generic;

public class GlobalClass
{
public class __sct_Town : BaseAgent
{
private int __sct_id { get => Fields["__sct_id"]; set => Fields["__sct_id"] = value; }
private int __sct_space { get => Fields["__sct_space"]; set => Fields["__sct_space"] = value; }

public __sct_Town(String state, IDictionary<String, dynamic> fields) : base(state, fields)
{
}

private bool __sct_Growing(IRuntimeContext ctx)
{
if (1 != 0)
{
Enter(ctx, "__sct_End");
return true;
}

Enter(ctx, "__sct_Growing");
return true;
return false;
}

private bool __sct_End(IRuntimeContext ctx)
{
ctx.ExitRuntime();
return true;
return false;
}

public override void Update(IRuntimeContext ctx)
{
_ = State switch
{
"__sct_Growing" => __sct_Growing(ctx),
"__sct_End" => __sct_End(ctx)};
}
}

public static void __sct_Setup(IRuntimeContext ctx)
{
ctx.AgentHandler.CreateAgent(new __sct_Town("__sct_Growing", new Dictionary<String, dynamic>(new KeyValuePair<String, dynamic>[] { new KeyValuePair<String, dynamic>("__sct_id", 1), new KeyValuePair<String, dynamic>("__sct_space", 50) })));
}

public static void RunSimulation(IRuntimeContext ctx)
{
Runtime runtime = new Runtime();
__sct_Setup(ctx);
runtime.Run(ctx);
}
}
}
37 changes: 37 additions & 0 deletions SocietalConstructionToolTests/SplitFileTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.CodeAnalysis;

using Sct;

namespace SocietalConstructionToolTests
{
[TestClass]
public class SplitFileTests : AbstractSnapshotTests
{
private static new IEnumerable<string[]> Files =>
Directory.GetFiles(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "TestFiles", "SplitFileTests"))
.Select(f => new[] { f });


[DataTestMethod]
public async Task RunFiles()
{
UseProjectRelativeDirectory("Snapshots/SplitFileTests"); // save snapshots here

var files = GetFiles();

var (outputText, errors) = SctRunner.CompileSct(files);

Assert.IsTrue(errors.Count() == 0, string.Join("\n", errors));
Assert.IsNotNull(outputText);
_ = await Verify(outputText)
.UseFileName(Path.GetFileNameWithoutExtension(files[0]));
}

// RunFiles is run for each file, and passing files to CompileSct only passes the file that triggered the test.
// This method is used to get all files to pass to CompileSct.
private static string[] GetFiles()
{
return Files.SelectMany(f => f).ToArray();
}
Comment on lines +30 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we generate two identical snapshots. Wouldn't it be better to just remove the dynamic stuff from this test so it functions as a single test case instead of two?
All that needs to be done is to remove the files argument.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function Setup() -> void {
// Create a town in the state growing with the id of 1 and space for 50 people
create Town::Growing(id: 1, space: 50);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Town(int id, int space) {
state Growing {
if (1) {
enter End;
}
enter Growing;
}
state End {
exit;
}
}
17 changes: 15 additions & 2 deletions SocietalConstructionToolTests/TypecheckerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,22 @@ public async Task TypecheckFile(string testFile)
_ = startNode.Accept(returnChecker);
errors.AddRange(returnChecker.Errors);

var sctTableVisitor = new SctTableVisitor();
var cTableBuilder = new CTableBuilder();

var sctTableVisitor = new SctTableVisitor(cTableBuilder);
_ = sctTableVisitor.Visit(startNode);
var ctable = sctTableVisitor.CTable;
var ctable = cTableBuilder.BuildCtable();

var setupType = ctable.GlobalClass.LookupFunctionType("Setup");
if (setupType is null)
{
errors.Add(new CompilerError("No setup function found"));
}
else if (setupType.ReturnType != TypeTable.Void || setupType.ParameterTypes.Count != 0)
{
errors.Add(new CompilerError("Setup function must return void and take no arguments"));
}

errors.AddRange(sctTableVisitor.Errors);

var sctTypeChecker = new SctTypeChecker(ctable);
Expand Down
Loading