Skip to content

Commit

Permalink
First steps to decompiler, typings
Browse files Browse the repository at this point in the history
  • Loading branch information
Vilsol committed Jan 22, 2022
1 parent bf9d98a commit 24bccab
Show file tree
Hide file tree
Showing 46 changed files with 1,768 additions and 449 deletions.
5 changes: 5 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ linters:
- goconst
- exportloopref
- durationcheck

linters-settings:
goconst:
min-len: 4
min-occurrences: 5
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ There are several example programs available on [the wiki](https://github.com/Vi
* `if`/`else if`/`else` statements
* `switch` statement
* `break`/`continue`/`fallthrough` statements
* Statement labeling
* `goto` statements
* Binary and Unary math
* Function level variable scopes
* Contextual errors
Expand All @@ -40,11 +42,14 @@ There are several example programs available on [the wiki](https://github.com/Vi
## In Progress

* MLOG Runtime
* MLOG to Go decompiler

## Roadmap

* Full variable block scoping
* Nested sub-selector support
* Merged compiler and decompiler registries
* Constant string and number slices

## Planned Optimizations

Expand All @@ -59,22 +64,29 @@ There are several example programs available on [the wiki](https://github.com/Vi
## Endgame Roadmap

* Transpilation optimizations
* MLOG to Go decompiler
* Support tail-recursion

## CLI Usage

```
Usage:
go-mlog transpile [flags] <program>
go-mlog [command]
Flags:
-h, --help help for transpile
Available Commands:
completion generate the autocompletion script for the specified shell
decompile Decompile MLOG to Go
execute Execute MLOG
help Help about any command
transpile Transpile Go to MLOG
trex Transpile Go to MLOG and execute it
Global Flags:
Flags:
--colors Force log output with colors
--comment-offset int Comment offset from line start (default 60)
--comments Output comments
-h, --help help for go-mlog
--log string The log level to output (default "info")
--metrics Output source metrics after execution
--numbers Output line numbers
--output string Output file. Outputs to stdout if unspecified
--source Output source code after comment
Expand Down
10 changes: 2 additions & 8 deletions checker/serializer.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package checker

import (
"encoding/json"
"fmt"
"go/ast"
"strings"
)
Expand All @@ -29,7 +27,7 @@ type serializedValue struct {
Comments []string `json:"comments,omitempty"`
}

func GetSerializablePackages() {
func GetSerializablePackages() map[string]map[string]interface{} {
result := make(map[string]map[string]interface{})

for pack, files := range packages {
Expand Down Expand Up @@ -68,9 +66,6 @@ func GetSerializablePackages() {
case *ast.GenDecl:
for _, spec := range castDecl.Specs {
switch castSpec := spec.(type) {
case *ast.TypeSpec:
// TODO
break
case *ast.ValueSpec:
var comments []string
if castSpec.Doc != nil {
Expand All @@ -94,8 +89,7 @@ func GetSerializablePackages() {
}
}

marshal, _ := json.Marshal(result)
fmt.Println(string(marshal))
return result
}

func serializeField(field *ast.Field) []serializedField {
Expand Down
35 changes: 35 additions & 0 deletions cmd/decompile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cmd

import (
"fmt"
"github.com/Vilsol/go-mlog/decompiler"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"io/ioutil"
)

func init() {
rootCmd.AddCommand(decompileCmd)
}

var decompileCmd = &cobra.Command{
Use: "decompile [flags] <program>",
Short: "Decompile MLOG to Go",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
result, err := decompiler.MLOGToGolangFile(args[0])
if err != nil {
return err
}

if output := viper.GetString("output"); output != "" {
if err := ioutil.WriteFile(output, []byte(result), 0644); err != nil {
return err
}
} else {
fmt.Println(result)
}

return nil
},
}
10 changes: 0 additions & 10 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,6 @@ var rootCmd = &cobra.Command{
}

func Execute() {
// Allow running from explorer
cobra.MousetrapHelpText = ""

// Execute transpile command as default
cmd, _, err := rootCmd.Find(os.Args[1:])
if (len(os.Args) <= 1 || os.Args[1] != "help") && (err != nil || cmd == rootCmd) {
args := append([]string{"transpile"}, os.Args[1:]...)
rootCmd.SetArgs(args)
}

if err := rootCmd.Execute(); err != nil {
panic(err)
}
Expand Down
23 changes: 23 additions & 0 deletions cmd/typings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package cmd

import (
"encoding/json"
"fmt"
"github.com/Vilsol/go-mlog/checker"
"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(typingsCmd)
}

var typingsCmd = &cobra.Command{
Use: "typings",
Short: "Output typings as JSON",
RunE: func(cmd *cobra.Command, args []string) error {
result := checker.GetSerializablePackages()
marshal, _ := json.Marshal(result)
fmt.Println(string(marshal))
return nil
},
}
142 changes: 142 additions & 0 deletions decompiler/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package decompiler

import (
"bytes"
"errors"
"github.com/Vilsol/go-mlog/mlog"
"go/ast"
"go/printer"
"go/token"
"io/ioutil"
"strconv"
)

const LabelPrefix = "jumpTo"

func MLOGToGolangFile(fileName string) (string, error) {
file, err := ioutil.ReadFile(fileName)

if err != nil {
return "", err
}

return MLOGToGolangBytes(file)
}

func MLOGToGolangBytes(input []byte) (string, error) {
return MLOGToGolang(string(input))
}

func MLOGToGolang(input string) (string, error) {
lines, _ := mlog.Tokenize(input)

global := &Global{
Lines: lines,
Labels: make(map[string]*mlog.MLOGLine),
MappedLines: make(map[int]*mlog.MLOGLine),
Variables: make(map[string]string),
}

allJumpTargets := make(map[int]bool)
for _, line := range lines {
// Detect "set @counter" and early exit
// TODO Benchmark
if len(line.Instruction) >= 2 && line.Instruction[0] == "set" && line.Instruction[1] == "@counter" {
return "", errors.New("decompiler does not support programs that set @counter variable")
}

tempLine := line
global.MappedLines[line.SourceLine] = &tempLine

if line.Label != "" {
global.Labels[line.Label] = &tempLine
}

if len(line.Instruction) > 0 {
translator, ok := funcTranslations[line.Instruction[0]]
if !ok {
return "", errors.New("unknown statement: " + line.Instruction[0])
}

if translator.Preprocess != nil {
jumpTargets, err := translator.Preprocess(line.Instruction[1:])
if err != nil {
return "", err
}

for _, target := range jumpTargets {
allJumpTargets[target] = true
}
}
}
}

allImports := make(map[string]bool)
statements := make([]ast.Stmt, 0)
for _, line := range lines {
// TODO Comments
if len(line.Instruction) > 0 {
translator := funcTranslations[line.Instruction[0]]

statement, imports, err := translator.Translate(line.Instruction[1:], global)
if err != nil {
return "", err
}

for _, s := range imports {
allImports[s] = true
}

labelName := line.Label
if labelName == "" {
if _, ok := allJumpTargets[line.SourceLine]; ok {
labelName = LabelPrefix + strconv.Itoa(line.SourceLine)
}
}

if labelName != "" && len(statement) > 0 {
statements = append(statements, &ast.LabeledStmt{
Label: ast.NewIdent(labelName),
Stmt: statement[0],
})
statements = append(statements, statement[1:]...)
} else {
statements = append(statements, statement...)
}
}
}

importSpecs := make([]ast.Spec, 0)
for s := range allImports {
importSpecs = append(importSpecs, &ast.ImportSpec{
Path: &ast.BasicLit{
Value: "\"" + s + "\"",
},
})
}

mainFile := &ast.File{
Name: ast.NewIdent("main"),
Decls: []ast.Decl{
&ast.GenDecl{
Tok: token.IMPORT,
Specs: importSpecs,
},
&ast.FuncDecl{
Name: ast.NewIdent("main"),
Type: &ast.FuncType{},
Body: &ast.BlockStmt{
List: statements,
},
},
},
}

var buf bytes.Buffer
fileSet := token.NewFileSet()
if err := printer.Fprint(&buf, fileSet, mainFile); err != nil {
return "", err
}

return buf.String(), nil
}
21 changes: 21 additions & 0 deletions decompiler/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package decompiler

import "go/ast"

type PreprocessFunc func(args []string) ([]int, error)
type TranslateFunc func(args []string, global *Global) ([]ast.Stmt, []string, error)

type Translator struct {
Preprocess PreprocessFunc
Translate TranslateFunc
}

var funcTranslations = map[string]Translator{}

func RegisterFuncTranslation(name string, translator Translator) {
if _, ok := funcTranslations[name]; ok {
panic("Function translation already exists: " + name)
}

funcTranslations[name] = translator
}
Loading

0 comments on commit 24bccab

Please sign in to comment.