diff --git a/Canon.Core/Abstractions/ICompilerLogger.cs b/Canon.Core/Abstractions/ICompilerLogger.cs new file mode 100644 index 0000000..69df92d --- /dev/null +++ b/Canon.Core/Abstractions/ICompilerLogger.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Logging; + +namespace Canon.Core.Abstractions; + +public interface ICompilerLogger : ILogger +{ + IDisposable ILogger.BeginScope(TState state) => default!; + + bool ILogger.IsEnabled(LogLevel logLevel) => true; + + public string Build(); +} diff --git a/Canon.Core/SemanticParser/CCodeGenerateVisitor.cs b/Canon.Core/SemanticParser/CCodeGenerateVisitor.cs index 0b68598..eb06da2 100644 --- a/Canon.Core/SemanticParser/CCodeGenerateVisitor.cs +++ b/Canon.Core/SemanticParser/CCodeGenerateVisitor.cs @@ -1,9 +1,10 @@ -using Canon.Core.CodeGenerators; +using Canon.Core.Abstractions; +using Canon.Core.CodeGenerators; using Canon.Core.SyntaxNodes; namespace Canon.Core.SemanticParser; -public class CCodeGenerateVisitor : TypeCheckVisitor +public class CCodeGenerateVisitor(ICompilerLogger? logger = null) : TypeCheckVisitor(logger) { public CCodeBuilder Builder { get; } = new(); diff --git a/Canon.Core/SemanticParser/TypeCheckVisitor.cs b/Canon.Core/SemanticParser/TypeCheckVisitor.cs index 15fb5c9..f8b6e9a 100644 --- a/Canon.Core/SemanticParser/TypeCheckVisitor.cs +++ b/Canon.Core/SemanticParser/TypeCheckVisitor.cs @@ -7,7 +7,7 @@ using Expression = Canon.Core.SyntaxNodes.Expression; namespace Canon.Core.SemanticParser; -public class TypeCheckVisitor(ILogger? logger = null) : SyntaxNodeVisitor +public class TypeCheckVisitor(ICompilerLogger? logger = null) : SyntaxNodeVisitor { public SymbolTable SymbolTable { get; private set; } = new(); diff --git a/Canon.Server/DataTransferObjects/CompileResponse.cs b/Canon.Server/DataTransferObjects/CompileResponse.cs index e5f3f08..2598410 100644 --- a/Canon.Server/DataTransferObjects/CompileResponse.cs +++ b/Canon.Server/DataTransferObjects/CompileResponse.cs @@ -20,6 +20,9 @@ public class CompileResponse [Required] public string CompileTime { get; set; } + [Required] + public string CompileInformation { get; set; } + public CompileResponse() { Id = string.Empty; @@ -27,6 +30,7 @@ public class CompileResponse CompiledCode = string.Empty; ImageAddress = string.Empty; CompileTime = string.Empty; + CompileInformation = string.Empty; } public CompileResponse(CompileResult result) @@ -36,5 +40,6 @@ public class CompileResponse CompiledCode = result.CompiledCode; ImageAddress = $"/api/file/{result.SytaxTreeImageFilename}"; CompileTime = result.CompileTime.AddHours(8).ToString("yyyy-MM-dd HH:mm:ss"); + CompileInformation = result.CompileInformation; } } diff --git a/Canon.Server/Entities/CompileResult.cs b/Canon.Server/Entities/CompileResult.cs index 4c16e9f..95c47c2 100644 --- a/Canon.Server/Entities/CompileResult.cs +++ b/Canon.Server/Entities/CompileResult.cs @@ -17,5 +17,7 @@ public class CompileResult public string CompiledCode { get; set; } = string.Empty; + public string CompileInformation { get; set; } = string.Empty; + public DateTime CompileTime { get; set; } } diff --git a/Canon.Server/Models/CompilerLogger.cs b/Canon.Server/Models/CompilerLogger.cs new file mode 100644 index 0000000..3afc5b8 --- /dev/null +++ b/Canon.Server/Models/CompilerLogger.cs @@ -0,0 +1,30 @@ +using System.Text; +using Canon.Core.Abstractions; + +namespace Canon.Server.Models; + +public class CompilerLogger : ICompilerLogger +{ + private readonly ThreadLocal _builder = new(() => new StringBuilder()); + + public string Build() + { + if (_builder.Value is not null) + { + string result = _builder.Value.ToString(); + _builder.Value.Clear(); + return result; + } + + throw new InvalidOperationException(); + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter) + { + if (_builder.Value is not null) + { + _builder.Value.Append(logLevel).Append(": ").Append(formatter(state, exception)).Append('\n'); + } + } +} diff --git a/Canon.Server/Program.cs b/Canon.Server/Program.cs index 94ca0b6..92d9832 100644 --- a/Canon.Server/Program.cs +++ b/Canon.Server/Program.cs @@ -3,6 +3,7 @@ using Canon.Core.GrammarParser; using Canon.Core.LexicalParser; using Canon.Core.SemanticParser; using Canon.Server.Extensions; +using Canon.Server.Models; using Canon.Server.Services; using Microsoft.EntityFrameworkCore; @@ -21,11 +22,13 @@ builder.Services.AddDbContext(options => options.UseMongoDB(connectionString, "Canon"); }); builder.Services.AddGridFs(connectionString, "Canon"); +builder.Services.AddSingleton(); builder.Services.AddTransient(); builder.Services.AddSingleton( _ => GeneratedGrammarParser.Instance); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddHostedService(); diff --git a/Canon.Server/Services/CompileDbContext.cs b/Canon.Server/Services/CompileDbContext.cs index b156192..afaa637 100644 --- a/Canon.Server/Services/CompileDbContext.cs +++ b/Canon.Server/Services/CompileDbContext.cs @@ -4,15 +4,10 @@ using MongoDB.EntityFrameworkCore.Extensions; namespace Canon.Server.Services; -public class CompileDbContext : DbContext +public class CompileDbContext(DbContextOptions options) : DbContext(options) { public DbSet CompileResults { get; init; } - public CompileDbContext(DbContextOptions options) : base(options) - { - - } - protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); diff --git a/Canon.Server/Services/CompilerService.cs b/Canon.Server/Services/CompilerService.cs index f5464d8..0cf48c5 100644 --- a/Canon.Server/Services/CompilerService.cs +++ b/Canon.Server/Services/CompilerService.cs @@ -13,6 +13,8 @@ public class CompilerService( ILexer lexer, IGrammarParser grammarParser, SyntaxTreeTraveller traveller, + CCodeGenerateVisitor visitor, + ICompilerLogger compilerLogger, CompileDbContext dbContext, GridFsService gridFsService, SyntaxTreePresentationService syntaxTreePresentationService, @@ -39,7 +41,6 @@ public class CompilerService( await using Stream imageStream = syntaxTreePresentationService.Present(root); string filename = await gridFsService.UploadStream(imageStream, "image/png"); - CCodeGenerateVisitor visitor = new(); traveller.Travel(root, visitor); CompileResult result = new() @@ -48,7 +49,8 @@ public class CompilerService( CompileId = Guid.NewGuid().ToString(), CompiledCode = visitor.Builder.Build(), SytaxTreeImageFilename = filename, - CompileTime = DateTime.Now + CompileTime = DateTime.Now, + CompileInformation = compilerLogger.Build() }; await dbContext.CompileResults.AddAsync(result); diff --git a/Canon.Server/client-app/src/Interfaces/OutputIntf.ts b/Canon.Server/client-app/src/Interfaces/OutputIntf.ts index c10524e..393867b 100644 --- a/Canon.Server/client-app/src/Interfaces/OutputIntf.ts +++ b/Canon.Server/client-app/src/Interfaces/OutputIntf.ts @@ -1,9 +1,5 @@ +import * as openapi from '../openapi'; + export interface OutputIntf { - - compiledCode: string, - id: string, - imageAddress: string, - sourceCode: string, - compileTime: string - + data : openapi.components["schemas"]["CompileResponse"] } diff --git a/Canon.Server/client-app/src/Pages/Index.tsx b/Canon.Server/client-app/src/Pages/Index.tsx index c80d62c..e7bfd52 100644 --- a/Canon.Server/client-app/src/Pages/Index.tsx +++ b/Canon.Server/client-app/src/Pages/Index.tsx @@ -7,8 +7,6 @@ import * as openapi from '../openapi'; import {enqueueSnackbar} from "notistack"; import {useNavigate} from "react-router-dom"; import {HistoryPage} from "./HistoryPage.tsx"; -import {OutputIntf} from "../Interfaces/OutputIntf.ts"; - const client = createClient(); @@ -16,12 +14,13 @@ const client = createClient(); export function Index() { const [inputValue, setInputValue] = useState(''); - const [outputValue, setOutputValue] = useState({ + const [outputValue, setOutputValue] = useState({ compiledCode: "", sourceCode: "", id: "", imageAddress: "", - compileTime: "" + compileTime: "", + compileInformation: "" }); const [historyPageState,setHistoryPageState] = useState(false); const navigate = useNavigate(); // 跳转hook @@ -36,7 +35,8 @@ export function Index() { sourceCode: "", id: "", imageAddress: "pic/uncompiled.png", - compileTime: "" + compileTime: "", + compileInformation: "" }) return; } @@ -52,13 +52,7 @@ export function Index() { }) if (data !== undefined) { setInputValue(data.sourceCode); - setOutputValue({ - compiledCode: data.compiledCode, - sourceCode: data.sourceCode, - id: data.id, - imageAddress: data.imageAddress, - compileTime: data.compileTime - }) + setOutputValue(data) } } getCompileInstance(); @@ -79,13 +73,7 @@ export function Index() { }) if (data !== undefined) { - setOutputValue({ - compiledCode: data.compiledCode, - sourceCode: data.sourceCode, - id: data.id, - imageAddress: data.imageAddress, - compileTime: data.compileTime - }) + setOutputValue(data); enqueueSnackbar("编译成功", {variant: "success", anchorOrigin: {vertical: 'bottom', horizontal: 'right'}}); navigate(`/${data.id}`, {}) diff --git a/Canon.Server/client-app/src/Pages/OutputField.tsx b/Canon.Server/client-app/src/Pages/OutputField.tsx index 1085f86..f1956c9 100644 --- a/Canon.Server/client-app/src/Pages/OutputField.tsx +++ b/Canon.Server/client-app/src/Pages/OutputField.tsx @@ -1,13 +1,14 @@ -import {CSSProperties, useState} from "react"; -import {Box, ToggleButton, ToggleButtonGroup} from "@mui/material"; -import {PhotoProvider, PhotoView} from "react-photo-view"; +import { CSSProperties, useState } from "react"; +import { Box, ToggleButton, ToggleButtonGroup } from "@mui/material"; +import { PhotoProvider, PhotoView } from "react-photo-view"; import MonacoEditor from "react-monaco-editor"; +import { OutputIntf } from "../Interfaces/OutputIntf"; - -// @ts-expect-error ... -export function OutputField({data}) { +export function OutputField(props: OutputIntf) { const [state, setState] = useState('tree') - const {imageAddress, compiledCode} = data; + const { imageAddress, compiledCode, compileInformation } = props.data; + + return <>
+ aria-label="code" + size={"small"}> Code + aria-label="tree" + size={"small"}> Tree + + Log + - - { - - state === 'tree' ? + { + state === 'tree' && {imageAddress == "pic/uncompiled.png" ? : + style={{ + width: "100%", + height: "auto" + }} + alt="" /> : + style={{ + objectFit: 'cover', + width: "100%", + height: "100%" + }} + alt="" /> } - : - } + } + { + state == "code" && + } + { + state == "log" && + }
diff --git a/Canon.Server/client-app/src/openapi.d.ts b/Canon.Server/client-app/src/openapi.d.ts index 942b9f5..a8fa80b 100644 --- a/Canon.Server/client-app/src/openapi.d.ts +++ b/Canon.Server/client-app/src/openapi.d.ts @@ -5,139 +5,140 @@ export interface paths { - "/api/Compiler": { - get: { - parameters: { - query?: { - start?: number; - end?: number; - }; - }; - responses: { - /** @description Success */ - 200: { - content: { - "text/plain": components["schemas"]["CompileResponse"][]; - "application/json": components["schemas"]["CompileResponse"][]; - "text/json": components["schemas"]["CompileResponse"][]; - }; - }; - }; + "/api/Compiler": { + get: { + parameters: { + query?: { + start?: number; + end?: number; }; - post: { - requestBody?: { - content: { - "application/json": components["schemas"]["SourceCode"]; - "text/json": components["schemas"]["SourceCode"]; - "application/*+json": components["schemas"]["SourceCode"]; - }; - }; - responses: { - /** @description Success */ - 200: { - content: { - "text/plain": components["schemas"]["CompileResponse"]; - "application/json": components["schemas"]["CompileResponse"]; - "text/json": components["schemas"]["CompileResponse"]; - }; - }; - }; - }; - delete: { - responses: { - /** @description No Content */ - 204: { - content: never; - }; - }; + }; + responses: { + /** @description Success */ + 200: { + content: { + "text/plain": components["schemas"]["CompileResponse"][]; + "application/json": components["schemas"]["CompileResponse"][]; + "text/json": components["schemas"]["CompileResponse"][]; + }; }; + }; }; - "/api/Compiler/{compileId}": { - get: { - parameters: { - path: { - compileId: string; - }; - }; - responses: { - /** @description Success */ - 200: { - content: { - "text/plain": components["schemas"]["CompileResponse"]; - "application/json": components["schemas"]["CompileResponse"]; - "text/json": components["schemas"]["CompileResponse"]; - }; - }; - }; + post: { + requestBody?: { + content: { + "application/json": components["schemas"]["SourceCode"]; + "text/json": components["schemas"]["SourceCode"]; + "application/*+json": components["schemas"]["SourceCode"]; }; - delete: { - parameters: { - path: { - compileId: string; - }; - }; - responses: { - /** @description No Content */ - 204: { - content: never; - }; - /** @description Not Found */ - 404: { - content: { - "text/plain": components["schemas"]["ProblemDetails"]; - "application/json": components["schemas"]["ProblemDetails"]; - "text/json": components["schemas"]["ProblemDetails"]; - }; - }; - }; + }; + responses: { + /** @description Success */ + 200: { + content: { + "text/plain": components["schemas"]["CompileResponse"]; + "application/json": components["schemas"]["CompileResponse"]; + "text/json": components["schemas"]["CompileResponse"]; + }; }; + }; }; - "/api/File/{filename}": { - get: { - parameters: { - path: { - filename: string; - }; - }; - responses: { - /** @description Success */ - 200: { - content: never; - }; - }; + delete: { + responses: { + /** @description No Content */ + 204: { + content: never; }; + }; }; + }; + "/api/Compiler/{compileId}": { + get: { + parameters: { + path: { + compileId: string; + }; + }; + responses: { + /** @description Success */ + 200: { + content: { + "text/plain": components["schemas"]["CompileResponse"]; + "application/json": components["schemas"]["CompileResponse"]; + "text/json": components["schemas"]["CompileResponse"]; + }; + }; + }; + }; + delete: { + parameters: { + path: { + compileId: string; + }; + }; + responses: { + /** @description No Content */ + 204: { + content: never; + }; + /** @description Not Found */ + 404: { + content: { + "text/plain": components["schemas"]["ProblemDetails"]; + "application/json": components["schemas"]["ProblemDetails"]; + "text/json": components["schemas"]["ProblemDetails"]; + }; + }; + }; + }; + }; + "/api/File/{filename}": { + get: { + parameters: { + path: { + filename: string; + }; + }; + responses: { + /** @description Success */ + 200: { + content: never; + }; + }; + }; + }; } export type webhooks = Record; export interface components { - schemas: { - CompileResponse: { - id: string; - sourceCode: string; - compiledCode: string; - imageAddress: string; - compileTime: string; - }; - ProblemDetails: { - type?: string | null; - title?: string | null; - /** Format: int32 */ - status?: number | null; - detail?: string | null; - instance?: string | null; - [key: string]: unknown; - }; - SourceCode: { - code: string; - }; + schemas: { + CompileResponse: { + id: string; + sourceCode: string; + compiledCode: string; + imageAddress: string; + compileTime: string; + compileInformation: string; }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + ProblemDetails: { + type?: string | null; + title?: string | null; + /** Format: int32 */ + status?: number | null; + detail?: string | null; + instance?: string | null; + [key: string]: unknown; + }; + SourceCode: { + code: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } export type $defs = Record; diff --git a/Canon.Tests/SemanticTests/TypeCheckVisitorTests.cs b/Canon.Tests/SemanticTests/TypeCheckVisitorTests.cs index 0f853b2..7c9dd95 100644 --- a/Canon.Tests/SemanticTests/TypeCheckVisitorTests.cs +++ b/Canon.Tests/SemanticTests/TypeCheckVisitorTests.cs @@ -7,7 +7,7 @@ namespace Canon.Tests.SemanticTests; public class TypeCheckVisitorTests(ITestOutputHelper testOutputHelper) { - private readonly TestLogger _logger = new(testOutputHelper); + private readonly TestLogger _logger = new(testOutputHelper); [Fact] public void ConstTypeTest() diff --git a/Canon.Tests/Utils/TestLogger.cs b/Canon.Tests/Utils/TestLogger.cs index 3e27bed..faf1230 100644 --- a/Canon.Tests/Utils/TestLogger.cs +++ b/Canon.Tests/Utils/TestLogger.cs @@ -1,24 +1,16 @@ -using Microsoft.Extensions.Logging; +using Canon.Core.Abstractions; +using Microsoft.Extensions.Logging; using Xunit.Abstractions; namespace Canon.Tests.Utils; -public class TestLogger(ITestOutputHelper testOutputHelper) : ILogger, IDisposable +public class TestLogger(ITestOutputHelper testOutputHelper) : ICompilerLogger { public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { - testOutputHelper.WriteLine("{0}: {1}", logLevel, formatter(state, exception)); + testOutputHelper.WriteLine($"{logLevel}: {formatter(state, exception)}"); } - public bool IsEnabled(LogLevel logLevel) => false; - - public void Dispose() - { - } - - public IDisposable BeginScope(TState state) where TState : notnull - { - return this; - } + public string Build() => string.Empty; }