Skip to content

Commit

Permalink
Added multifile support (#182)
Browse files Browse the repository at this point in the history
  • Loading branch information
HPT-I authored Apr 17, 2024
1 parent 00dcd13 commit 4b86480
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 42 deletions.
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();
}
}
}
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

0 comments on commit 4b86480

Please sign in to comment.