fix:修复前端bug (#83)

Co-authored-by: jackfiled <xcrenchangjun@outlook.com>
Reviewed-on: PostGuard/Canon#83
Co-authored-by: ichirinko <1621543655@qq.com>
Co-committed-by: ichirinko <1621543655@qq.com>
This commit is contained in:
ichirinko 2024-05-13 22:29:32 +08:00 committed by jackfiled
parent d90f6fde62
commit 20e82c6f4f
10 changed files with 109 additions and 55 deletions

View File

@ -0,0 +1,19 @@
namespace Canon.Core.Exceptions;
/// <summary>
/// 编译器中的统一异常基类
/// </summary>
public class CanonException : Exception
{
public CanonException()
{
}
public CanonException(string message) : base(message)
{
}
public CanonException(string message, Exception innerException) : base(message, innerException)
{
}
}

View File

@ -8,7 +8,7 @@ namespace Canon.Core.Exceptions;
/// <summary> /// <summary>
/// 语法分析中引发的异常 /// 语法分析中引发的异常
/// </summary> /// </summary>
public class GrammarException : Exception public class GrammarException : CanonException
{ {
public override string Message { get; } public override string Message { get; }

View File

@ -3,32 +3,32 @@ using Enums;
/// <summary> /// <summary>
/// 词法分析中引发的异常 /// 词法分析中引发的异常
/// </summary> /// </summary>
public class LexemeException : Exception public class LexemeException : CanonException
{ {
public LexemeErrorType ErrorType { get; } public LexemeErrorType ErrorType { get; }
public uint Line { get; } public uint Line { get; }
public uint CharPosition { get; } public uint CharPosition { get; }
public LexemeException() { }
public LexemeException(string message) : base(message) { } private readonly string _message;
public LexemeException(string message, Exception innerException) :
base(message, innerException) { }
/// <param name="errorType">错误类型</param> /// <param name="errorType">错误类型</param>
/// <param name="line">单词的行号</param> /// <param name="line">单词的行号</param>
/// <param name="charPosition">单词的列号</param> /// <param name="charPosition">单词的列号</param>
/// <param name="message">错误信息</param> /// <param name="message">错误信息</param>
public LexemeException(LexemeErrorType errorType, uint line, uint charPosition, string message) : public LexemeException(LexemeErrorType errorType, uint line, uint charPosition, string message)
base("line:" + line + ", charPosition:" + charPosition + " :" + message)
{ {
ErrorType = errorType; ErrorType = errorType;
Line = line; Line = line;
CharPosition = charPosition; CharPosition = charPosition;
_message = message;
} }
public override string Message => ToString();
public override string ToString() public override string ToString()
{ {
return $"LexemeException: ErrorType={ErrorType}, Line={Line}, CharPosition={CharPosition}, Message={Message}\n"; return $"LexemeException: ErrorType={ErrorType}, Line={Line}, CharPosition={CharPosition}, Message={_message}\n";
} }
} }

View File

@ -3,6 +3,7 @@ using Canon.Server.Entities;
using Canon.Server.Services; using Canon.Server.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MongoDB.Bson;
namespace Canon.Server.Controllers; namespace Canon.Server.Controllers;
@ -35,7 +36,7 @@ public class CompilerController(CompileDbContext dbContext, CompilerService comp
public async Task<ActionResult<CompileResponse>> GetResponse(string compileId) public async Task<ActionResult<CompileResponse>> GetResponse(string compileId)
{ {
CompileResult? result = await (from item in dbContext.CompileResults.AsNoTracking() CompileResult? result = await (from item in dbContext.CompileResults.AsNoTracking()
where item.CompileId == compileId where item.Id == new ObjectId(compileId)
select item).FirstOrDefaultAsync(); select item).FirstOrDefaultAsync();
if (result is null) if (result is null)
@ -59,7 +60,7 @@ public class CompilerController(CompileDbContext dbContext, CompilerService comp
public async Task<IActionResult> DeleteCompileResult(string compileId) public async Task<IActionResult> DeleteCompileResult(string compileId)
{ {
CompileResult? result = await (from item in dbContext.CompileResults CompileResult? result = await (from item in dbContext.CompileResults
where item.CompileId == compileId where item.Id == new ObjectId(compileId)
select item).FirstOrDefaultAsync(); select item).FirstOrDefaultAsync();
if (result is null) if (result is null)

View File

@ -8,6 +8,9 @@ public class CompileResponse
[Required] [Required]
public string Id { get; set; } public string Id { get; set; }
[Required]
public bool Error { get; set; }
[Required] [Required]
public string SourceCode { get; set; } public string SourceCode { get; set; }
@ -35,7 +38,8 @@ public class CompileResponse
public CompileResponse(CompileResult result) public CompileResponse(CompileResult result)
{ {
Id = result.CompileId; Id = result.Id.ToString();
Error = result.Error;
SourceCode = result.SourceCode; SourceCode = result.SourceCode;
CompiledCode = result.CompiledCode; CompiledCode = result.CompiledCode;
ImageAddress = $"/api/file/{result.SytaxTreeImageFilename}"; ImageAddress = $"/api/file/{result.SytaxTreeImageFilename}";

View File

@ -7,8 +7,7 @@ public class CompileResult
{ {
public ObjectId Id { get; set; } public ObjectId Id { get; set; }
[MaxLength(40)] public bool Error { get; set; }
public string CompileId { get; set; } = string.Empty;
public string SourceCode { get; set; } = string.Empty; public string SourceCode { get; set; } = string.Empty;

View File

@ -22,7 +22,6 @@ builder.Services.AddDbContext<CompileDbContext>(options =>
options.UseMongoDB(connectionString, "Canon"); options.UseMongoDB(connectionString, "Canon");
}); });
builder.Services.AddGridFs(connectionString, "Canon"); builder.Services.AddGridFs(connectionString, "Canon");
builder.Services.AddSingleton<ICompilerLogger, CompilerLogger>();
builder.Services.AddTransient<ILexer, Lexer>(); builder.Services.AddTransient<ILexer, Lexer>();
builder.Services.AddSingleton<IGrammarParser>( builder.Services.AddSingleton<IGrammarParser>(
_ => GeneratedGrammarParser.Instance); _ => GeneratedGrammarParser.Instance);

View File

@ -1,4 +1,5 @@
using Canon.Core.Abstractions; using Canon.Core.Abstractions;
using Canon.Core.Exceptions;
using Canon.Core.LexicalParser; using Canon.Core.LexicalParser;
using Canon.Core.SemanticParser; using Canon.Core.SemanticParser;
using Canon.Core.SyntaxNodes; using Canon.Core.SyntaxNodes;
@ -6,6 +7,7 @@ using Canon.Server.DataTransferObjects;
using Canon.Server.Entities; using Canon.Server.Entities;
using Canon.Server.Models; using Canon.Server.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MongoDB.Bson;
namespace Canon.Server.Services; namespace Canon.Server.Services;
@ -13,7 +15,6 @@ public class CompilerService(
ILexer lexer, ILexer lexer,
IGrammarParser grammarParser, IGrammarParser grammarParser,
SyntaxTreeTraveller traveller, SyntaxTreeTraveller traveller,
ICompilerLogger compilerLogger,
CompileDbContext dbContext, CompileDbContext dbContext,
GridFsService gridFsService, GridFsService gridFsService,
SyntaxTreePresentationService syntaxTreePresentationService, SyntaxTreePresentationService syntaxTreePresentationService,
@ -34,19 +35,44 @@ public class CompilerService(
} }
CodeReader reader = new(sourceCode); CodeReader reader = new(sourceCode);
ProgramStruct root;
try
{
IEnumerable<SemanticToken> tokens = lexer.Tokenize(reader); IEnumerable<SemanticToken> tokens = lexer.Tokenize(reader);
ProgramStruct root = grammarParser.Analyse(tokens); root = grammarParser.Analyse(tokens);
}
catch (CanonException e)
{
CompileResult errorResult = new()
{
Id = ObjectId.GenerateNewId(),
Error = true,
SourceCode = sourceCode.Code,
CompiledCode = string.Empty,
SytaxTreeImageFilename = string.Empty,
CompileTime = DateTime.Now,
CompileInformation = e.Message
};
await dbContext.CompileResults.AddAsync(errorResult);
await dbContext.SaveChangesAsync();
return new CompileResponse(errorResult);
}
await using Stream imageStream = syntaxTreePresentationService.Present(root); await using Stream imageStream = syntaxTreePresentationService.Present(root);
string filename = await gridFsService.UploadStream(imageStream, "image/png"); string filename = await gridFsService.UploadStream(imageStream, "image/png");
ICompilerLogger compilerLogger = new CompilerLogger();
CodeGeneratorVisitor visitor = new(compilerLogger); CodeGeneratorVisitor visitor = new(compilerLogger);
traveller.Travel(root, visitor); traveller.Travel(root, visitor);
CompileResult result = new() CompileResult result = new()
{ {
Id = ObjectId.GenerateNewId(),
Error = visitor.IsError,
SourceCode = sourceCode.Code, SourceCode = sourceCode.Code,
CompileId = Guid.NewGuid().ToString(),
CompiledCode = visitor.Builder.Build(), CompiledCode = visitor.Builder.Build(),
SytaxTreeImageFilename = filename, SytaxTreeImageFilename = filename,
CompileTime = DateTime.Now, CompileTime = DateTime.Now,

View File

@ -1,12 +1,12 @@
import {AppBar, Button, Grid, Toolbar, Typography} from "@mui/material"; import { AppBar, Button, Grid, Toolbar, Typography } from "@mui/material";
import {InputField} from "./InputField.tsx"; import { InputField } from "./InputField.tsx";
import {CSSProperties, useEffect, useState} from "react"; import { CSSProperties, useEffect, useState } from "react";
import {OutputField} from "./OutputField.tsx"; import { OutputField } from "./OutputField.tsx";
import createClient from "openapi-fetch"; import createClient from "openapi-fetch";
import * as openapi from '../openapi'; import * as openapi from '../openapi';
import {enqueueSnackbar} from "notistack"; import { enqueueSnackbar } from "notistack";
import {useNavigate} from "react-router-dom"; import { useNavigate } from "react-router-dom";
import {HistoryPage} from "./HistoryPage.tsx"; import { HistoryPage } from "./HistoryPage.tsx";
const client = createClient<openapi.paths>(); const client = createClient<openapi.paths>();
@ -16,13 +16,14 @@ export function Index() {
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [outputValue, setOutputValue] = useState<openapi.components["schemas"]["CompileResponse"]>({ const [outputValue, setOutputValue] = useState<openapi.components["schemas"]["CompileResponse"]>({
compiledCode: "", compiledCode: "",
error: false,
sourceCode: "", sourceCode: "",
id: "", id: "",
imageAddress: "", imageAddress: "",
compileTime: "", compileTime: "",
compileInformation: "" compileInformation: ""
}); });
const [historyPageState,setHistoryPageState] = useState(false); const [historyPageState, setHistoryPageState] = useState(false);
const navigate = useNavigate(); // 跳转hook const navigate = useNavigate(); // 跳转hook
useEffect(() => { useEffect(() => {
@ -32,6 +33,7 @@ export function Index() {
setInputValue(""); setInputValue("");
setOutputValue({ setOutputValue({
compiledCode: "", compiledCode: "",
error: false,
sourceCode: "", sourceCode: "",
id: "", id: "",
imageAddress: "pic/uncompiled.png", imageAddress: "pic/uncompiled.png",
@ -41,7 +43,7 @@ export function Index() {
return; return;
} }
const getCompileInstance = async () => { const getCompileInstance = async () => {
const {data} = await client.GET("/api/Compiler/{compileId}", { const { data } = await client.GET("/api/Compiler/{compileId}", {
params: params:
{ {
path: path:
@ -66,21 +68,24 @@ export function Index() {
async function compilerButtonClick() { async function compilerButtonClick() {
const {data} = await client.POST("/api/Compiler", { const { data } = await client.POST("/api/Compiler", {
body: { body: {
code: inputValue code: inputValue
} }
}) });
if (data !== undefined) { if (data == undefined) {
setOutputValue(data); enqueueSnackbar("内部错误", { variant: "error", anchorOrigin: { vertical: 'bottom', horizontal: 'right' } });
enqueueSnackbar("编译成功", {variant: "success", anchorOrigin: {vertical: 'bottom', horizontal: 'right'}}); return;
navigate(`/${data.id}`, {}) }
if (!data.error) {
enqueueSnackbar("编译成功", { variant: "success", anchorOrigin: { vertical: 'bottom', horizontal: 'right' } });
} else { } else {
// error // error
enqueueSnackbar("编译失败", {variant: "error", anchorOrigin: {vertical: 'bottom', horizontal: 'right'}}); enqueueSnackbar("编译失败", { variant: "error", anchorOrigin: { vertical: 'bottom', horizontal: 'right' } });
} }
navigate(`/${data.id}`, {})
} }
function historyButtonClick() { function historyButtonClick() {
@ -90,8 +95,8 @@ export function Index() {
return <> return <>
<div className={"title"} <div className={"title"}
style={titleClassCss}> style={titleClassCss}>
<AppBar style={{zIndex: 0}}> <AppBar style={{ zIndex: 0 }}>
<Toolbar style={{width: '100%'}}> <Toolbar style={{ width: '100%' }}>
<Typography variant="h6"> <Typography variant="h6">
Canon Canon
</Typography> </Typography>
@ -124,18 +129,18 @@ export function Index() {
<div className={"content"} <div className={"content"}
style={contentClassCss}> style={contentClassCss}>
<Grid container spacing={2} style={{width: "100%", height: "100%"}}> <Grid container spacing={2} style={{ width: "100%", height: "100%" }}>
<Grid item xs={12} sm={6} style={{width: "100%", height: "100%"}}> <Grid item xs={12} sm={6} style={{ width: "100%", height: "100%" }}>
<InputField defaultValue={inputValue} onValueChange={handleValueChange}> <InputField defaultValue={inputValue} onValueChange={handleValueChange}>
</InputField> </InputField>
</Grid> </Grid>
<Grid item xs={12} sm={6} style={{width: "100%", height: "100%"}}> <Grid item xs={12} sm={6} style={{ width: "100%", height: "100%" }}>
<OutputField data={outputValue}> <OutputField data={outputValue}>
</OutputField> </OutputField>
</Grid> </Grid>
</Grid> </Grid>
</div> </div>
<HistoryPage state = {historyPageState} setState={setHistoryPageState}> <HistoryPage state={historyPageState} setState={setHistoryPageState}>
</HistoryPage> </HistoryPage>
</> </>

View File

@ -115,6 +115,7 @@ export interface components {
schemas: { schemas: {
CompileResponse: { CompileResponse: {
id: string; id: string;
error: boolean;
sourceCode: string; sourceCode: string;
compiledCode: string; compiledCode: string;
imageAddress: string; imageAddress: string;