Skip to content
This repository has been archived by the owner on Jan 4, 2023. It is now read-only.

Commit

Permalink
Roslyn now outputs line/character info for syntax and runtime errors
Browse files Browse the repository at this point in the history
  • Loading branch information
drakewill-CRL committed Dec 20, 2020
1 parent 6da3fb3 commit 5d48414
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 23 deletions.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,39 @@
using PixelVision8.Engine.Chips;
using PixelVision8.Engine;
using System.IO;

// Pixel Vision 8 - New Template Script [C#]
// Copyright (C) 2017, Pixel Vision 8 (@pixelvision8)
// Converted from the Lua file by Drake Williams [drakewill+pv8@gmail.com]

namespace PixelVisionRoslyn
{
public class RoslynGameChip : PixelVision8.Engine.Chips.GameChip
{
public override void Init()
{
BackgroundColor(12);
var message = "EMPTY GAME\n\n\nThis is an empty C# game template.\n\n\nVisit 'www.pixelvision8.com' to learn more about creating games from scratch.";

var wrap = WordWrap(message, (display.X / 8) - 2);
var lines = SplitLines(wrap);
var total = lines.Length;
var startY = ((display.Y / 8) - 1) - total;

// We want to render the text from the bottom of the screen so we offset
// it and loop backwards.
for (var i = total - 1; i >= 0; i--)
DrawText(lines[i], 1, startY + (i - 1), DrawMode.Tile, "large", 15);
}

public override void Draw()
{

}

public override void Update(int timeDelta)
{
RedrawDisplay();
}
}
}
Expand Up @@ -16,7 +16,7 @@
of fonts into the default.font.png. Use uppercase for larger characters and
lowercase for a smaller one.
]]--
local message = "EMPTY GAME\n\n\nThis is an empty game template.\n\n\nVisit 'www.pixelvision8.com' to learn more about creating games from scratch."
local message = "EMPTY GAME\n\n\nThis is an empty Lua game template.\n\n\nVisit 'www.pixelvision8.com' to learn more about creating games from scratch."

--[[
The Init() method is part of the game's lifecycle and called a game starts.
Expand Down
5 changes: 5 additions & 0 deletions PixelVision8.CoreDesktop.csproj
Expand Up @@ -38,6 +38,11 @@
</ItemGroup>

<ItemGroup>
<Content Remove="Content\PixelVisionOS\Tools\ErrorTool\medium.font.png" />
</ItemGroup>

<ItemGroup>
<Compile Include="Disks\PixelVisionOS\System\Tools\WorkspaceTool\ProjectTemplate\code.cs" />
<Compile Include="Program.cs" />
<Compile Include="SDK\Editor\Audio\SfxrMusicGeneratorChip.cs" />
<Compile Include="SDK\Editor\Editors\GameEditor.cs" />
Expand Down
54 changes: 32 additions & 22 deletions SDK/Runner/DesktopRunner.cs
Expand Up @@ -16,10 +16,13 @@
// Christer Kaitila - @McFunkypants
// Pedro Medeiros - @saint11
// Shawn Rakowski - @shwany
// Drake Williams - drakewill+pv8@gmail.com
//

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MoonSharp.Interpreter;
Expand Down Expand Up @@ -762,7 +765,7 @@ protected override void Update(GameTime gameTime)
{
DisplayError(ErrorCode.Exception,
new Dictionary<string, string>
{{"@{error}", e is ScriptRuntimeException error ? error.DecoratedMessage : e.Message}},
{{"@{error}", e is ScriptRuntimeException error ? error.DecoratedMessage : e.Message + e.StackTrace.Split("\r\n")[0]}},
e);
}
}
Expand Down Expand Up @@ -1557,27 +1560,28 @@ protected void ParseFiles(string[] files, SaveFlags? flags = null)
loadService.ParseFiles(files, _tmpEngine, flags.Value);
}

public void CompileFromSource(string[] files)
public void CompileFromSource(string[] files, bool buildDebugData = true)
{
var total = files.Length;

var syntaxTrees = new SyntaxTree[total];
var embeddedTexts = new EmbeddedText[total];

for (var i = 0;
i < total;
i++)
for (var i = 0; i < total; i++)
{
var path = WorkspacePath.Parse(files[i]);

var data = workspaceService.ReadTextFromFile(path);

syntaxTrees[i] = CSharpSyntaxTree.ParseText(data);
//if (buildDebugData)
//{
var st = SourceText.From(text: data, encoding: Encoding.UTF8); //, canBeEmbedded: true isnt present when passing in a string?
embeddedTexts[i] = EmbeddedText.FromSource(files[i], st);
//}
}

//Compilation options, should line up 1:1 with Visual Studio since it's the same underlying compiler.
var options = new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release,
optimizationLevel: buildDebugData ? OptimizationLevel.Debug : OptimizationLevel.Release,
moduleName: "RoslynGame");

//All of these are mandatory. This appears to the be minimum needed. Uncertain as of initial PoC if anything else outside of this needs referenced.
Expand All @@ -1598,41 +1602,47 @@ public void CompileFromSource(string[] files)
var compiler = CSharpCompilation.Create("LoadedGame", syntaxTrees, references, options);

//Compile the existing file into memory, or error out.
var stream = new MemoryStream();
var dllStream = new MemoryStream();
var pdbStream = new MemoryStream();

//This wont help unless we hit a runtime error.
var emitOptions = new EmitOptions(
debugInformationFormat: DebugInformationFormat.PortablePdb,
pdbFilePath: "RoslynGame.pdb"
);

var compileResults = compiler.Emit(stream);
var compileResults = compiler.Emit( peStream: dllStream, pdbStream: pdbStream, embeddedTexts:embeddedTexts, options: emitOptions);
if (compileResults.Success)
{
stream.Seek(0, SeekOrigin.Begin);
dllStream.Seek(0, SeekOrigin.Begin);
pdbStream.Seek(0, SeekOrigin.Begin);
}

else
{
var errors = compileResults.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList();

// TODO Need to get the error from the compiler
// var e = "Code could not be compiled";
var errorData = errors[0].Location.GetLineSpan();
var lineNumber = errorData.StartLinePosition.Line + 1;
var charNumber = errorData.StartLinePosition.Character + 1;
DisplayError(ErrorCode.Exception,
new Dictionary<string, string>
{
{
"@{error}",
errors.Count > 0
? errors[0].GetMessage()
? "Line " + lineNumber + " Pos " + charNumber + ": " + errors[0].GetMessage()
: "There was an unknown errror trying to compile a C# file."
}
});
//TODO: error handling, use data from compileResults to show user what's wrong.
return;
}

//Get the DLL into active memory so we can use it.
var roslynassembly = stream.ToArray();
var loadedAsm = Assembly.Load(roslynassembly);
//Get the DLL into active memory so we can use it. Runtime errors will give the wrong line number if we're in Release mode.
var loadedAsm = Assembly.Load(dllStream.ToArray(), buildDebugData ? pdbStream.ToArray() : null);

var roslynGameChipType =
loadedAsm.GetType("PixelVisionRoslyn.RoslynGameChip"); //This type much match what's in code.cs.
//Could theoretically iterate over types until once that inherits from GameChip is found, but this Proof of Concept demonstrates the baseline feature.
loadedAsm.GetType("PixelVisionRoslyn.RoslynGameChip"); //code.cs must use this type.
//Could theoretically iterate over types until one that inherits from GameChip is found, but this strictness may be a better idea.

if (roslynGameChipType != null)
{
Expand Down

0 comments on commit 5d48414

Please sign in to comment.