Skip to content

Commit

Permalink
feat: handle parser errors (#324)
Browse files Browse the repository at this point in the history
The editor will no longer fail silently when there are errors in DM source code.
  • Loading branch information
SpaiR authored Nov 19, 2024
1 parent 06f3e62 commit b9d3bc4
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 33 deletions.
28 changes: 20 additions & 8 deletions internal/app/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"runtime"
"sdmm/third_party/sdmmparser"
"time"

"sdmm/internal/app/ui/cpwsarea/workspace"
Expand Down Expand Up @@ -135,26 +136,37 @@ func (a *app) forceLoadEnvironment(path string, callback func()) {
go func() {
dlg := makeLoadingDialog(path)
dialog.Open(dlg)
defer dialog.Close(dlg)

start := time.Now()
log.Printf("parsing environment: [%s]...", path)

env, err := dmenv.New(path)

if err != nil {
log.Print("unable to open environment:", err)
dialog.Close(dlg)
dialog.Open(dialog.TypeInformation{
Title: "Error!",
Information: "Unable to open environment: " + path,
})
log.Print("unable to open environment by path:", path, err)

if sdmmparser.IsParserError(err) {
dialog.Open(dialog.TypeCustom{
Title: "Parser Error!",
CloseButton: true,
Layout: w.Layout{
w.Text("Unable to open environment: " + path),
w.Separator(),
w.Text(err.Error()),
},
})
} else {
dialog.Open(dialog.TypeInformation{
Title: "Error!",
Information: "Unable to open environment: " + path,
})
}
return
}

log.Printf("environment [%s] parsed in [%d] ms", path, time.Since(start).Milliseconds())

dialog.Close(dlg)

window.RunLater(func() {
afterLoad(env)
})
Expand Down
3 changes: 1 addition & 2 deletions internal/dmapi/dmenv/dme.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package dmenv

import (
"fmt"
"path/filepath"
"sdmm/third_party/sdmmparser"
"strings"
Expand All @@ -27,7 +26,7 @@ func New(path string) (*Dme, error) {

objectTreeType, err := sdmmparser.ParseEnvironment(path)
if err != nil {
return nil, fmt.Errorf("[dmenv] unable to create dme by path [%s]: %w", path, err)
return nil, err
}

traverseTree0(objectTreeType, "", nil, &dme)
Expand Down
18 changes: 18 additions & 0 deletions third_party/sdmmparser/sdmmparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package sdmmparser
import "C"
import (
"encoding/json"
"errors"
"fmt"
"strings"
"unsafe"
Expand All @@ -31,6 +32,20 @@ type ObjectTreeVar struct {
IsStatic bool `json:"is_static"`
}

type parserError struct {
msg string
}

func (e *parserError) Error() string {
return e.msg
}

func IsParserError(err error) bool {
var e *parserError
ok := errors.As(err, &e)
return ok
}

func ParseEnvironment(environmentPath string) (*ObjectTreeType, error) {
nativePath := C.CString(environmentPath)
defer C.free(unsafe.Pointer(nativePath))
Expand All @@ -39,6 +54,9 @@ func ParseEnvironment(environmentPath string) (*ObjectTreeType, error) {
defer C.SdmmFreeStr(nativeStr)

str := C.GoString(nativeStr)
if strings.HasPrefix(str, "parser error") {
return nil, &parserError{msg: str}
}
if strings.HasPrefix(str, "error") {
return nil, fmt.Errorf(str)
}
Expand Down
67 changes: 52 additions & 15 deletions third_party/sdmmparser/src/environment.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::panic;

use dm::constants::Constant;
use dm::Context;
use dm::objtree::TypeRef;
use dm::Context;

#[derive(Serialize)]
struct ObjectTreeType {
Expand All @@ -22,11 +22,9 @@ struct ObjectTreeVar {
}

pub fn parse_environment(path: String) -> String {
match panic::catch_unwind(|| {
match parse(&path) {
Some(json) => json,
None => format!("error: unable to parse environment {}", path)
}
match panic::catch_unwind(|| match parse(&path) {
Some(json) => json,
None => format!("parser error: unable to parse environment {}", path),
}) {
Ok(result) => result,
Err(e) => {
Expand All @@ -40,15 +38,45 @@ pub fn parse_environment(path: String) -> String {
}

fn parse(env_path: &str) -> Option<String> {
let objtree = match Context::default().parse_environment(env_path.as_ref()) {
Ok(t) => t,
Err(_e) => return None,
};
let ctx = Context::default();
let objtree = ctx.parse_environment(env_path.as_ref()).ok()?;

if let Some(m) = errors_message(&ctx) {
return Some(m);
}

let root = recurse_objtree(objtree.root());
let json = serde_json::to_string(&root).unwrap();
serde_json::to_string(&root).ok()
}

return Some(json);
fn errors_message(ctx: &Context) -> Option<String> {
let ctx_e = ctx.errors();
let errors: Vec<_> = ctx_e
.iter()
.filter(|e| e.severity() <= dm::Severity::Info)
.collect();

if errors.is_empty() {
return None;
}

let msg = errors
.iter()
.filter_map(|e| {
ctx.file_path(e.location().file).to_str().map(|file_path| {
format!(
" {} - [{}:{}] | {}",
file_path,
e.location().line,
e.location().column,
e.description(),
)
})
})
.collect::<Vec<_>>()
.join("\n");

Some(format!("parser error: compilation errors\n{}", msg))
}

fn recurse_objtree(ty: TypeRef) -> ObjectTreeType {
Expand All @@ -68,9 +96,18 @@ fn recurse_objtree(ty: TypeRef) -> ObjectTreeType {
.unwrap_or(Constant::null())
.to_string(),
decl: var.declaration.is_some(),
is_tmp: var.declaration.as_ref().map_or(false, |d| d.var_type.flags.is_tmp()),
is_const: var.declaration.as_ref().map_or(false, |d| d.var_type.flags.is_const()),
is_static: var.declaration.as_ref().map_or(false, |d| d.var_type.flags.is_static()),
is_tmp: var
.declaration
.as_ref()
.map_or(false, |d| d.var_type.flags.is_tmp()),
is_const: var
.declaration
.as_ref()
.map_or(false, |d| d.var_type.flags.is_const()),
is_static: var
.declaration
.as_ref()
.map_or(false, |d| d.var_type.flags.is_static()),
});
}

Expand Down
13 changes: 5 additions & 8 deletions third_party/sdmmparser/src/icon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ struct IconState {
}

pub fn parse_icon_metadata(path: String) -> String {
match panic::catch_unwind(|| {
match parse(&path) {
Some(json) => json,
None => format!("error: unable to parse icon metadata {}", path)
}
match panic::catch_unwind(|| match parse(&path) {
Some(json) => json,
None => format!("error: unable to parse icon metadata {}", path),
}) {
Ok(result) => result,
Err(e) => {
Expand All @@ -40,9 +38,8 @@ fn parse(path: &str) -> Option<String> {
for text_chunk in &reader.info().compressed_latin1_text {
if text_chunk.keyword.eq("Description") {
return text_chunk.get_text().map_or(None, |info| {
Metadata::meta_from_str(info.as_str()).map_or(None, |metadata| {
Some(meta2json(metadata))
})
Metadata::meta_from_str(info.as_str())
.map_or(None, |metadata| Some(meta2json(metadata)))
});
}
}
Expand Down

0 comments on commit b9d3bc4

Please sign in to comment.