Commit 697b2687 authored by Jesse McGrew's avatar Jesse McGrew

Added a Blazor WebAssembly project to compile ZIL games in the browser.

parent b27696b58ceb
......@@ -66,6 +66,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Package", "Package", "{3E28
Package\zip\Zip.Packaging.targets = Package\zip\Zip.Packaging.targets
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Zilf.Playground", "src\Zilf.Playground\Zilf.Playground.csproj", "{D38CAE84-836D-416D-83F6-DE2F2502658C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CI|Any CPU = CI|Any CPU
......@@ -219,6 +221,18 @@ Global
{55BC8693-4255-4F7C-B69D-734406DE6C88}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{55BC8693-4255-4F7C-B69D-734406DE6C88}.Release|x86.ActiveCfg = Release|Any CPU
{55BC8693-4255-4F7C-B69D-734406DE6C88}.Release|x86.Build.0 = Release|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.CI|Any CPU.ActiveCfg = Debug|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.CI|Any CPU.Build.0 = Debug|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.CI|x86.ActiveCfg = Debug|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.CI|x86.Build.0 = Debug|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.Debug|x86.ActiveCfg = Debug|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.Debug|x86.Build.0 = Debug|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.Release|Any CPU.Build.0 = Release|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.Release|x86.ActiveCfg = Release|Any CPU
{D38CAE84-836D-416D-83F6-DE2F2502658C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -242,6 +256,7 @@ Global
{7C9E4970-9997-451B-BCDA-FB869C753F59} = {1E9A9589-73D4-466C-9A3D-D44D058B2359}
{25194AC6-1151-463C-82EA-5D5AD9B56F8D} = {1E9A9589-73D4-466C-9A3D-D44D058B2359}
{3E280263-C59C-4A3B-B9ED-466D22C25333} = {1E9A9589-73D4-466C-9A3D-D44D058B2359}
{D38CAE84-836D-416D-83F6-DE2F2502658C} = {639D8160-38EF-4ED1-8202-8B8CFBE22FBD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {774D42F0-105F-4B51-BC60-F4661B610BAC}
......
......@@ -8,8 +8,8 @@
<ItemGroup>
<PackageReference Include="JunitXml.TestLogger" Version="2.1.78" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="16.9.1024">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="16.9.1025">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
......
......@@ -27,7 +27,7 @@ using Zilf.Common.StringEncoding;
namespace Zapf
{
class AbbrevFinder
public sealed class AbbrevFinder
{
public struct Result
{
......
......@@ -29,9 +29,9 @@ using Zilf.Common;
namespace Zapf
{
delegate IDebugFileWriter GetDebugWriterDelegate(Stream stream);
public delegate IDebugFileWriter GetDebugWriterDelegate(Stream stream);
class Context : IErrorSink, IDisposable
public sealed class Context : IErrorSink, IDisposable
{
public bool Quiet, InformMode, ListAddresses, AbbreviateMode, XmlDebugMode;
public string? InFile, OutFile, DebugFile;
......@@ -924,7 +924,7 @@ namespace Zapf
}
}
enum SymbolType
public enum SymbolType
{
/// <summary>
/// The symbol has not been defined.
......@@ -956,7 +956,7 @@ namespace Zapf
Object,
}
sealed class Symbol
public sealed class Symbol
{
/// <summary>
/// The symbol's name in the source code.
......@@ -994,7 +994,7 @@ namespace Zapf
}
}
sealed class Fixup
public sealed class Fixup
{
public Fixup(string symbol) => Symbol = symbol;
......
......@@ -51,14 +51,14 @@ namespace Zapf
public bool? Exists { get; set; }
}
class InitializingContextEventArgs : EventArgs
public class InitializingContextEventArgs : EventArgs
{
public InitializingContextEventArgs(Context ctx) => Context = ctx;
public Context Context { get; }
}
readonly struct AssemblyResult
public readonly struct AssemblyResult
{
public AssemblyResult(bool success, Context? context)
{
......@@ -73,7 +73,7 @@ namespace Zapf
public Context? Context { get; }
}
sealed class ZapfAssembler
public sealed class ZapfAssembler
{
public IFileSystem FileSystem { get; set; } = PhysicalFileSystem.Instance;
......
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
@page "/"
@using Zapf
@using Zilf.Common
@using Zilf.Compiler
@using Zilf.Diagnostics
<h1>ZIL Playground</h1>
<div>
<div style="margin:10px 0;">
Theme:
<select @onchange="ChangeTheme">
<option value="vs">Visual Studio</option>
<option value="vs-dark">Visual Studio Dark</option>
<option value="hc-black">High Contrast Black</option>
</select>
</div>
@*<div style="margin:10px 0;">
New Value: <input type="text" @bind="ValueToSet" style="width: 400px;" /> <button @onclick="SetValue">Set Value</button>
</div>
<div style="margin:10px 0;">
<button @onclick="GetValue">Get Value</button>
</div>
<div style="margin:10px 0;">
<button @onclick="AddCommand">Add Command (Ctrl+Enter)</button>
</div>
<div style="margin:10px 0;">
<button @onclick="AddAction">Add Action (Ctrl+D)</button>
</div>
<div style="margin:10px 0;">
See the console for results.
</div>*@
<div style="margin:10px 0;">
<button @onclick="Compile">Compile</button>
</div>
</div>
<MonacoEditor @ref="_editor"
Id="code-editor"
ConstructionOptions="EditorConstructionOptions"
OnDidInit="EditorOnDidInit"
OnContextMenu="EditorOnContextMenu"
/>
<CompilerOutput CompileResult="@CompileResult"
AssembleResult="@AssembleResult"
FileSystem="@FileSystem" />
@code {
private MonacoEditor _editor { get; set; }
private string ValueToSet { get; set; }
private FrontEndResult CompileResult { get; set; }
private AssemblyResult? AssembleResult { get; set; }
private InMemoryFileSystem FileSystem { get; set; }
private StandaloneEditorConstructionOptions EditorConstructionOptions(MonacoEditor editor)
{
return new StandaloneEditorConstructionOptions
{
Language = "zil",
GlyphMargin = true,
Value = "<VERSION ZIP>\n" +
"\n" +
"<ROUTINE GO ()\n" +
" <TELL \"Hello world!\" CR>\n" +
" <QUIT>>",
};
}
private async Task EditorOnDidInit(MonacoEditorBase editor)
{
await _editor.AddCommand((int)KeyMode.CtrlCmd | (int)KeyCode.KEY_H, (editor, keyCode) =>
{
Console.WriteLine("Ctrl+H : Initial editor command is triggered.");
System.Diagnostics.Debugger.Break();
});
//var newDecorations = new ModelDeltaDecoration[]
//{
// new ModelDeltaDecoration
// {
// Range = new BlazorMonaco.Bridge.Range(3,1,3,1),
// Options = new ModelDecorationOptions
// {
// IsWholeLine = true,
// ClassName = "decorationContentClass",
// GlyphMarginClassName = "decorationGlyphMarginClass"
// }
// }
//};
//decorationIds = await _editor.DeltaDecorations(null, newDecorations);
// You can now use 'decorationIds' to change or remove the decorations
}
//private string[] decorationIds;
private void EditorOnContextMenu(EditorMouseEvent eventArg)
{
Console.WriteLine("OnContextMenu : " + System.Text.Json.JsonSerializer.Serialize(eventArg));
}
private async Task ChangeTheme(ChangeEventArgs e)
{
Console.WriteLine($"setting theme to: {e.Value.ToString()}");
await MonacoEditor.SetTheme(e.Value.ToString());
}
private async Task SetValue()
{
Console.WriteLine($"setting value to: {ValueToSet}");
await _editor.SetValue(ValueToSet);
}
private async Task GetValue()
{
var val = await _editor.GetValue();
Console.WriteLine($"value is: {val}");
}
private async Task AddCommand()
{
await _editor.AddCommand((int)KeyMode.CtrlCmd | (int)KeyCode.Enter, (editor, keyCode) =>
{
Console.WriteLine("Ctrl+Enter : Editor command is triggered.");
});
}
private async Task AddAction()
{
await _editor.AddAction("testAction", "Test Action", new int[] { (int)KeyMode.CtrlCmd | (int)KeyCode.KEY_D, (int)KeyMode.CtrlCmd | (int)KeyCode.KEY_B }, null, null, "navigation", 1.5, (editor, keyCodes) =>
{
Console.WriteLine("Ctrl+D : Editor action is triggered.");
});
}
private async Task Compile()
{
var fileSystem = new InMemoryFileSystem
{
["main.zil"] = await _editor.GetValue(),
};
var compiler = new FrontEnd { FileSystem = fileSystem, Logger = NullDiagnosticLogger.Instance };
var compResult = compiler.Compile("main.zil", "main.zap", false);
CompileResult = compResult;
AssembleResult = null;
FileSystem = fileSystem;
if (compResult.Success)
{
var assembler = new Zapf.ZapfAssembler { FileSystem = fileSystem };
AssembleResult = assembler.Assemble("main.zap", "main.z#");
}
}
}
\ No newline at end of file
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Zilf.Playground
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DeleteExistingFiles>True</DeleteExistingFiles>
<ExcludeApp_Data>False</ExcludeApp_Data>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>D:\working\heptapod\zilf\bin\Release\net5.0\browser-wasm\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>net5.0</TargetFramework>
<ProjectGuid>d38cae84-836d-416d-83f6-de2f2502658c</ProjectGuid>
</PropertyGroup>
</Project>
\ No newline at end of file
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:51833",
"sslPort": 44314
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Zilf.Playground": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@inject IJSRuntime JS
@using System.Text.RegularExpressions
@using Zapf
@using Zilf.Common
@using Zilf.Compiler
<h2>Compilation Results</h2>
@if (CompileResult == null)
{
<p>No compilation requested.</p>
}
else
{
@if (AssembleResult != null)
{
<h3>Story File</h3>
if (!AssembleResult.Value.Success)
{
<p>Assembly failed. See below.</p>
}
else
{
<button @onclick="DownloadStoryFile">Download @StoryFileName</button>
}
}
<h3>Details</h3>
<table>
<tr>
<th>Success?</th>
<td>@CompileResult.Success</td>
</tr>
<tr>
<th>Errors</th>
<td>@CompileResult.ErrorCount</td>
</tr>
<tr>
<th>Warnings</th>
<td>@CompileResult.WarningCount (@CompileResult.SuppressedWarningCount suppressed)</td>
</tr>
</table>
@if (CompileResult.Diagnostics.Count > 0)
{
<h3>Diagnostics</h3>
<table>
<tr>
<th>Severity</th>
<th>Code</th>
<th>Description</th>
<th>Location</th>
</tr>
@foreach (var diag in CompileResult.Diagnostics)
{
<tr>
<td>@diag.Severity</td>
<td>@diag.Code</td>
<td>@diag.GetFormattedMessage()</td>
<td>@diag.Location.SourceInfo</td>
</tr>
}
</table>
}
@if (CompileResult.Success)
{
<h3>Assembly Code</h3>
<table>
@foreach (var (path, text) in GetZapOutput())
{
<tr @key="@path">
<th style="vertical-align: top">@path</th>
<td><pre>@text</pre></td>
</tr>
}
</table>
}
}
@code {
[Parameter]
public FrontEndResult CompileResult { get; set; }
[Parameter]
public AssemblyResult? AssembleResult { get; set; }
[Parameter]
public InMemoryFileSystem FileSystem { get; set; }
private static Regex StoryFileRegExp = new Regex(@"\.z\d$", RegexOptions.IgnoreCase);
private string StoryFileName => FileSystem?.Paths.FirstOrDefault(StoryFileRegExp.IsMatch);
private IEnumerable<(string path, string text)> GetZapOutput()
{
if (CompileResult == null)
return Enumerable.Empty<(string, string)>();
return from path in FileSystem.Paths
where path.EndsWith(".zap")
select (path, FileSystem[path]);
}
private void DownloadStoryFile()
{
var path = StoryFileName;
if (path != null)
{
var file = FileSystem.GetBytes(path);
string filename = System.IO.Path.GetFileName(path);
string contentType = "application/x-zmachine";
var jsu = (IJSUnmarshalledRuntime)JS;
jsu.InvokeUnmarshalled<string, string, byte[], bool>("BlazorDownloadFileFast", filename, contentType, file);
}
}
}
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4">
<a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
</div>
.page {
position: relative;
display: flex;
flex-direction: column;
}
.main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row:not(.auth) {
display: none;
}
.top-row.auth {
justify-content: space-between;
}
.top-row a, .top-row .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.main > div {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">Zilf.Playground</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
@*<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>*@
</ul>
</div>
@code {
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.oi {
width: 2rem;
font-size: 1.1rem;
vertical-align: text-top;
top: -2px;
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}