From 24bccab09b2b9fc77e7a6db5c8970e4cd115c0c7 Mon Sep 17 00:00:00 2001 From: Vilsol Date: Sat, 22 Jan 2022 23:33:08 +0200 Subject: [PATCH] First steps to decompiler, typings --- .golangci.yaml | 5 + README.md | 22 +- checker/serializer.go | 10 +- cmd/decompile.go | 35 +++ cmd/root.go | 10 - cmd/typings.go | 23 ++ decompiler/main.go | 142 +++++++++ decompiler/registry.go | 21 ++ decompiler/translations_native.go | 107 +++++++ decompiler/types.go | 122 ++++++++ m/base.go | 2 +- m/control.go | 10 +- m/impl/base.go | 223 ++++++++++++++ m/impl/control.go | 157 +++++++++- m/impl/draw.go | 289 ++++++++++++++++++ m/impl/operations.go | 310 +++++++++++++++++++- m/impl/types.go | 5 + mlog/tokenizer.go | 101 +++++++ runtime/parser.go | 83 +----- runtime/types.go | 9 +- tests/runtime/memory_test.go | 1 + tests/runtime/operation_test.go | 3 +- tests/runtime/tokenizer_test.go | 6 +- tests/transpiler/base_test.go | 29 +- tests/transpiler/constant_test.go | 6 +- tests/transpiler/control_test.go | 41 +-- tests/transpiler/custom_test.go | 25 +- tests/transpiler/draw_test.go | 25 +- tests/transpiler/errors_test.go | 6 +- tests/transpiler/extra_test.go | 25 +- tests/transpiler/native_test.go | 25 +- tests/transpiler/operator_test.go | 69 +---- tests/transpiler/stacked_function_test.go | 6 +- tests/transpiler/stackless_function_test.go | 6 +- tests/transpiler/statement_test.go | 56 ++-- tests/transpiler/type_test.go | 25 +- tests/transpiler/unit_control_test.go | 25 +- tests/transpiler/unit_test.go | 25 +- tests/transpiler/utils.go | 28 ++ transpiler/main.go | 12 +- transpiler/statement.go | 31 ++ transpiler/type_label.go | 22 ++ transpiler/type_mlog.go | 4 + transpiler/type_native.go | 4 + transpiler/types.go | 3 +- wasm/main.go | 23 +- 46 files changed, 1768 insertions(+), 449 deletions(-) create mode 100644 cmd/decompile.go create mode 100644 cmd/typings.go create mode 100644 decompiler/main.go create mode 100644 decompiler/registry.go create mode 100644 decompiler/translations_native.go create mode 100644 decompiler/types.go create mode 100644 mlog/tokenizer.go create mode 100644 transpiler/type_label.go diff --git a/.golangci.yaml b/.golangci.yaml index 5df1ca4..6853dd8 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -31,3 +31,8 @@ linters: - goconst - exportloopref - durationcheck + +linters-settings: + goconst: + min-len: 4 + min-occurrences: 5 diff --git a/README.md b/README.md index fcd31ea..bd85ee4 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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] + 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 diff --git a/checker/serializer.go b/checker/serializer.go index 477b912..2f441be 100644 --- a/checker/serializer.go +++ b/checker/serializer.go @@ -1,8 +1,6 @@ package checker import ( - "encoding/json" - "fmt" "go/ast" "strings" ) @@ -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 { @@ -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 { @@ -94,8 +89,7 @@ func GetSerializablePackages() { } } - marshal, _ := json.Marshal(result) - fmt.Println(string(marshal)) + return result } func serializeField(field *ast.Field) []serializedField { diff --git a/cmd/decompile.go b/cmd/decompile.go new file mode 100644 index 0000000..31f3779 --- /dev/null +++ b/cmd/decompile.go @@ -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] ", + 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 + }, +} diff --git a/cmd/root.go b/cmd/root.go index edeba1a..e684a12 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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) } diff --git a/cmd/typings.go b/cmd/typings.go new file mode 100644 index 0000000..2173e65 --- /dev/null +++ b/cmd/typings.go @@ -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 + }, +} diff --git a/decompiler/main.go b/decompiler/main.go new file mode 100644 index 0000000..039c4b4 --- /dev/null +++ b/decompiler/main.go @@ -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 +} diff --git a/decompiler/registry.go b/decompiler/registry.go new file mode 100644 index 0000000..d9fd09f --- /dev/null +++ b/decompiler/registry.go @@ -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 +} diff --git a/decompiler/translations_native.go b/decompiler/translations_native.go new file mode 100644 index 0000000..4a56ac4 --- /dev/null +++ b/decompiler/translations_native.go @@ -0,0 +1,107 @@ +package decompiler + +import ( + "errors" + "go/ast" + "go/token" + "strconv" +) + +func init() { + RegisterFuncTranslation("print", Translator{ + Translate: func(args []string, global *Global) ([]ast.Stmt, []string, error) { + if len(args) != 1 { + return nil, nil, errors.New("expecting 1 argument") + } + + return []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: ast.NewIdent("print"), + Args: []ast.Expr{ + ast.NewIdent(args[0]), + }, + }, + }, + }, nil, nil + }, + }) + + RegisterFuncTranslation("jump", Translator{ + Preprocess: func(args []string) ([]int, error) { + if len(args) != 1 { + return nil, errors.New("expecting 1 argument") + } + + if jumpTarget, err := strconv.ParseInt(args[0], 10, 64); err == nil { + return []int{int(jumpTarget)}, nil + } + + return nil, nil + }, + Translate: func(args []string, global *Global) ([]ast.Stmt, []string, error) { + if len(args) != 1 { + return nil, nil, errors.New("expecting 1 argument") + } + + line, ok := global.Labels[args[0]] + var labelName string + if ok { + labelName = line.Label + } else { + if jumpTarget, err := strconv.ParseInt(args[0], 10, 64); err == nil { + jumpTargetInt := int(jumpTarget) + targetLine, ok := global.MappedLines[jumpTargetInt] + if ok && targetLine.Label != "" { + labelName = targetLine.Label + } else { + labelName = LabelPrefix + strconv.Itoa(jumpTargetInt) + } + } else { + return nil, nil, errors.New("unknown jump target: " + args[0]) + } + } + + return []ast.Stmt{ + &ast.BranchStmt{ + Tok: token.GOTO, + Label: ast.NewIdent(labelName), + }, + }, nil, nil + }, + }) + + RegisterFuncTranslation("set", Translator{ + Translate: func(args []string, global *Global) ([]ast.Stmt, []string, error) { + if len(args) != 2 { + return nil, nil, errors.New("expecting 2 arguments") + } + + expectedType := "string" + if _, err := strconv.ParseInt(args[1], 10, 64); err == nil { + expectedType = "int" + } else if _, err := strconv.ParseFloat(args[1], 64); err == nil { + expectedType = "float64" + } else if storedType, ok := global.Variables[args[1]]; ok { + expectedType = storedType + } + + tok, err := global.AssignOrDefine(args[0], expectedType) + if err != nil { + return nil, nil, err + } + + return []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{ + ast.NewIdent(args[0]), + }, + Tok: tok, + Rhs: []ast.Expr{ + ast.NewIdent(args[1]), + }, + }, + }, []string{"github.com/Vilsol/go-mlog/m"}, nil + }, + }) +} diff --git a/decompiler/types.go b/decompiler/types.go new file mode 100644 index 0000000..4124719 --- /dev/null +++ b/decompiler/types.go @@ -0,0 +1,122 @@ +package decompiler + +import ( + "context" + "errors" + "github.com/Vilsol/go-mlog/mlog" + "go/ast" + "go/token" + "strconv" + "strings" +) + +type Global struct { + Lines []mlog.MLOGLine + Labels map[string]*mlog.MLOGLine + MappedLines map[int]*mlog.MLOGLine + Variables map[string]string +} + +func (g Global) AssignOrDefine(variable string, variableType string) (token.Token, error) { + if storedType, ok := g.Variables[variable]; ok { + if variableType != storedType { + return 0, errors.New("attempting to assign type " + variableType + " to " + variable + " [" + storedType + "]") + } + + return token.ASSIGN, nil + } + + g.Variables[variable] = variableType + + return token.DEFINE, nil +} + +func (g Global) Exists(variable string, variableType string) (bool, error) { + if storedType, ok := g.Variables[variable]; ok { + if variableType != storedType { + return true, errors.New("attempting to use " + variable + " [" + storedType + "] as " + variableType) + } + + return true, nil + } + + return false, nil +} + +func (g Global) Resolve(str string, variableType string) (ast.Expr, error) { + if str == "@this" { + return &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("This"), + }, nil + } + + if variableType == "string" && strings.HasPrefix(str, "@") { + return &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("Const"), + }, + Args: []ast.Expr{ + ast.NewIdent("\"" + str + "\""), + }, + }, nil + } + + exists, err := g.Exists(str, variableType) + if exists { + if err != nil { + return nil, err + } + + return ast.NewIdent(str), nil + } + + return &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("B"), + }, + Args: []ast.Expr{ + ast.NewIdent("\"" + str + "\""), + }, + }, nil +} + +func (g Global) GetType(variable string) (string, error) { + if storedType, ok := g.Variables[variable]; ok { + return storedType, nil + } + + return "", errors.New("variable not defined: " + variable) +} + +func (g Global) Assert(variable string, variableType string) error { + if variableType == "int" { + if _, err := strconv.ParseInt(variable, 10, 64); err == nil { + return nil + } + } + + if variableType == "float64" { + if _, err := strconv.ParseFloat(variable, 64); err == nil { + return nil + } + } + + storedType, err := g.GetType(variable) + if err != nil { + return err + } + + if storedType != variableType { + return errors.New("provided variable " + variable + " [" + storedType + "] cannot be used as " + variableType) + } + + return nil +} + +type Resolvable interface { + PostProcess(context.Context, *Global) error + GetValue() string +} diff --git a/m/base.go b/m/base.go index 22b2451..669b373 100644 --- a/m/base.go +++ b/m/base.go @@ -8,7 +8,7 @@ func Read(memory string, position int) int { // Write a float64 value to memory at specified position // // For integer equivalent use WriteInt -func Write(value int, memory string, position int) { +func Write(value float64, memory string, position int) { } // Write an integer value to memory at specified position diff --git a/m/control.go b/m/control.go index ed03933..4777765 100644 --- a/m/control.go +++ b/m/control.go @@ -1,13 +1,13 @@ package m -// Enable/Disable an block e.g. conveyor, door, switch -func ControlEnabled(target string, enabled bool) { +// Enable/Disable a block e.g. conveyor, door, switch +func ControlEnabled(target Building, enabled bool) { } // Shoot with the provided turret at the target absolute position // // If shoot parameter is false, it will cease firing -func ControlShoot(turret string, x int, y int, shoot bool) { +func ControlShoot(turret Building, x int, y int, shoot bool) { } // Smart version of ControlShoot @@ -15,9 +15,9 @@ func ControlShoot(turret string, x int, y int, shoot bool) { // Shoot with the provided turret at the predicted position of target unit // // If shoot parameter is false, it will cease firing -func ControlShootP(turret string, target int, shoot bool) { +func ControlShootP(turret Building, target HealthC, shoot bool) { } // Set the configuration of the target building -func ControlConfigure(target string, configuration int) { +func ControlConfigure(target Building, configuration int) { } diff --git a/m/impl/base.go b/m/impl/base.go index 2bebe65..2eaa81b 100644 --- a/m/impl/base.go +++ b/m/impl/base.go @@ -2,11 +2,18 @@ package impl import ( "errors" + "github.com/Vilsol/go-mlog/decompiler" "github.com/Vilsol/go-mlog/transpiler" + "go/ast" "strings" ) func init() { + initBaseTranspiler() + initBaseDecompiler() +} + +func initBaseTranspiler() { transpiler.RegisterFuncTranslation("m.Read", transpiler.Translator{ Count: func(args []transpiler.Resolvable, vars []transpiler.Resolvable) int { return 1 @@ -142,3 +149,219 @@ func init() { }, }) } + +func initBaseDecompiler() { + decompiler.RegisterFuncTranslation("read", decompiler.Translator{ + Translate: func(args []string, global *decompiler.Global) ([]ast.Stmt, []string, error) { + if len(args) != 3 { + return nil, nil, errors.New("expecting 3 arguments") + } + + tok, err := global.AssignOrDefine(args[0], "int") + if err != nil { + return nil, nil, err + } + + return []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{ + ast.NewIdent(args[0]), + }, + Tok: tok, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("Read"), + }, + Args: []ast.Expr{ + ast.NewIdent("\"" + args[1] + "\""), + ast.NewIdent(args[2]), + }, + }, + }, + }, + }, []string{"github.com/Vilsol/go-mlog/m"}, nil + }, + }) + decompiler.RegisterFuncTranslation("write", decompiler.Translator{ + Translate: func(args []string, global *decompiler.Global) ([]ast.Stmt, []string, error) { + if len(args) != 3 { + return nil, nil, errors.New("expecting 3 arguments") + } + + calledFunc := "Write" + + variableType, err := global.GetType(args[0]) + if err != nil { + return nil, nil, err + } + + if variableType == "int" { + calledFunc = "WriteInt" + } + + return []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent(calledFunc), + }, + Args: []ast.Expr{ + ast.NewIdent(args[0]), + ast.NewIdent("\"" + args[1] + "\""), + ast.NewIdent(args[2]), + }, + }, + }, + }, []string{"github.com/Vilsol/go-mlog/m"}, nil + }, + }) + decompiler.RegisterFuncTranslation("printflush", decompiler.Translator{ + Translate: func(args []string, global *decompiler.Global) ([]ast.Stmt, []string, error) { + if len(args) != 1 { + return nil, nil, errors.New("expecting 1 argument") + } + + return []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("PrintFlush"), + }, + Args: []ast.Expr{ + ast.NewIdent("\"" + args[0] + "\""), + }, + }, + }, + }, []string{"github.com/Vilsol/go-mlog/m"}, nil + }, + }) + decompiler.RegisterFuncTranslation("getlink", decompiler.Translator{ + Translate: func(args []string, global *decompiler.Global) ([]ast.Stmt, []string, error) { + if len(args) != 2 { + return nil, nil, errors.New("expecting 2 arguments") + } + + tok, err := global.AssignOrDefine(args[0], "github.com/Vilsol/go-mlog/m.Link") + if err != nil { + return nil, nil, err + } + + return []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{ + ast.NewIdent(args[0]), + }, + Tok: tok, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("GetLink"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[1]), + }, + }, + }, + }, + }, []string{"github.com/Vilsol/go-mlog/m"}, nil + }, + }) + decompiler.RegisterFuncTranslation("radar", decompiler.Translator{ + Translate: func(args []string, global *decompiler.Global) ([]ast.Stmt, []string, error) { + if len(args) != 7 { + return nil, nil, errors.New("expecting 7 arguments") + } + + target1 := "m.RT" + strings.ToUpper(args[0][:1]) + args[0][1:] + target2 := "m.RT" + strings.ToUpper(args[1][:1]) + args[1][1:] + target3 := "m.RT" + strings.ToUpper(args[2][:1]) + args[2][1:] + sort := "m.RS" + strings.ToUpper(args[3][:1]) + args[3][1:] + + source, err := global.Resolve(args[5], "github.com/Vilsol/go-mlog/m.Ranged") + if err != nil { + return nil, nil, err + } + + tok, err := global.AssignOrDefine(args[6], "github.com/Vilsol/go-mlog/m.Unit") + if err != nil { + return nil, nil, err + } + + return []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{ + ast.NewIdent(args[6]), + }, + Tok: tok, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("Radar"), + }, + Args: []ast.Expr{ + source, + ast.NewIdent(target1), + ast.NewIdent(target2), + ast.NewIdent(target3), + ast.NewIdent(args[5]), + ast.NewIdent(sort), + }, + }, + }, + }, + }, []string{"github.com/Vilsol/go-mlog/m"}, nil + }, + }) + decompiler.RegisterFuncTranslation("sensor", decompiler.Translator{ + Translate: func(args []string, global *decompiler.Global) ([]ast.Stmt, []string, error) { + if len(args) != 3 { + return nil, nil, errors.New("expecting 3 arguments") + } + + tok, err := global.AssignOrDefine(args[0], "float64") + if err != nil { + return nil, nil, err + } + + source, err := global.Resolve(args[1], "github.com/Vilsol/go-mlog/m.HealthC") + if err != nil { + return nil, nil, err + } + + return []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{ + ast.NewIdent(args[0]), + }, + Tok: tok, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("Sensor"), + }, + Args: []ast.Expr{ + source, + &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("Const"), + }, + Args: []ast.Expr{ + ast.NewIdent("\"" + args[2] + "\""), + }, + }, + }, + }, + }, + }, + }, []string{"github.com/Vilsol/go-mlog/m"}, nil + }, + }) +} diff --git a/m/impl/control.go b/m/impl/control.go index 7c04997..10c3a80 100644 --- a/m/impl/control.go +++ b/m/impl/control.go @@ -1,8 +1,18 @@ package impl -import "github.com/Vilsol/go-mlog/transpiler" +import ( + "errors" + "github.com/Vilsol/go-mlog/decompiler" + "github.com/Vilsol/go-mlog/transpiler" + "go/ast" +) func init() { + initControlTranspiler() + initControlDecompiler() +} + +func initControlTranspiler() { transpiler.RegisterFuncTranslation("m.ControlEnabled", transpiler.Translator{ Count: func(args []transpiler.Resolvable, vars []transpiler.Resolvable) int { return 1 @@ -28,3 +38,148 @@ func init() { Translate: genBasicFuncTranslation([]string{"control", "configure"}, 2, 0), }) } + +func initControlDecompiler() { + decompiler.RegisterFuncTranslation("control", decompiler.Translator{ + Translate: func(args []string, global *decompiler.Global) ([]ast.Stmt, []string, error) { + if len(args) < 3 { + return nil, nil, errors.New("expecting at least 3 arguments") + } + + source, err := global.Resolve(args[1], "github.com/Vilsol/go-mlog/m.Building") + if err != nil { + return nil, nil, err + } + + var result []ast.Stmt + + switch args[0] { + case "enabled": + enabled := args[2] + if enabled != TRUE && enabled != FALSE && enabled != "1" && enabled != "0" { + if err := global.Assert(enabled, "bool"); err != nil { + return nil, nil, err + } + } else if enabled == "1" { + enabled = TRUE + } else if enabled == "0" { + enabled = FALSE + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("ControlEnabled"), + }, + Args: []ast.Expr{ + source, + ast.NewIdent(enabled), + }, + }, + }, + } + case "shoot": + if len(args) < 5 { + return nil, nil, errors.New("expecting at least 5 arguments") + } + + shoot := args[4] + if shoot != TRUE && shoot != FALSE && shoot != "1" && shoot != "0" { + if err := global.Assert(shoot, "bool"); err != nil { + return nil, nil, err + } + } else if shoot == "1" { + shoot = TRUE + } else if shoot == "0" { + shoot = FALSE + } + + if err := global.Assert(args[2], "int"); err != nil { + return nil, nil, err + } + + if err := global.Assert(args[3], "int"); err != nil { + return nil, nil, err + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("ControlShoot"), + }, + Args: []ast.Expr{ + source, + ast.NewIdent(args[2]), + ast.NewIdent(args[3]), + ast.NewIdent(shoot), + }, + }, + }, + } + case "shootp": + if len(args) < 4 { + return nil, nil, errors.New("expecting at least 4 arguments") + } + + shoot := args[3] + if shoot != "true" && shoot != "false" && shoot != "1" && shoot != "0" { + if err := global.Assert(shoot, "bool"); err != nil { + return nil, nil, err + } + } else if shoot == "1" { + shoot = "true" + } else if shoot == "0" { + shoot = "false" + } + + target, err := global.Resolve(args[2], "github.com/Vilsol/go-mlog/m.HealthC") + if err != nil { + return nil, nil, err + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("ControlShootP"), + }, + Args: []ast.Expr{ + source, + target, + ast.NewIdent(shoot), + }, + }, + }, + } + case "config": + if err := global.Assert(args[2], "int"); err != nil { + return nil, nil, err + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("ControlConfigure"), + }, + Args: []ast.Expr{ + source, + ast.NewIdent(args[2]), + }, + }, + }, + } + default: + return nil, nil, errors.New("unknown control command: " + args[0]) + } + + return result, []string{"github.com/Vilsol/go-mlog/m"}, nil + }, + }) +} diff --git a/m/impl/draw.go b/m/impl/draw.go index 82a2ec8..11cb22a 100644 --- a/m/impl/draw.go +++ b/m/impl/draw.go @@ -1,11 +1,19 @@ package impl import ( + "errors" + "github.com/Vilsol/go-mlog/decompiler" "github.com/Vilsol/go-mlog/transpiler" + "go/ast" "strings" ) func init() { + initDrawTranspiler() + initDrawDecompiler() +} + +func initDrawTranspiler() { transpiler.RegisterFuncTranslation("m.DrawClear", transpiler.Translator{ Count: func(args []transpiler.Resolvable, vars []transpiler.Resolvable) int { return 1 @@ -100,3 +108,284 @@ func init() { }, }) } + +func initDrawDecompiler() { + decompiler.RegisterFuncTranslation("draw", decompiler.Translator{ + Translate: func(args []string, global *decompiler.Global) ([]ast.Stmt, []string, error) { + if len(args) < 2 { + return nil, nil, errors.New("expecting at least 2 arguments") + } + + var result []ast.Stmt + + switch args[0] { + case "clear": + if len(args) < 4 { + return nil, nil, errors.New("expecting at least 4 arguments") + } + + for i := 1; i < 4; i++ { + if err := global.Assert(args[i], "int"); err != nil { + return nil, nil, err + } + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("DrawClear"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[1]), + ast.NewIdent(args[2]), + ast.NewIdent(args[3]), + }, + }, + }, + } + case "color": + if len(args) < 5 { + return nil, nil, errors.New("expecting at least 5 arguments") + } + + for i := 1; i < 5; i++ { + if err := global.Assert(args[i], "int"); err != nil { + return nil, nil, err + } + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("DrawColor"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[1]), + ast.NewIdent(args[2]), + ast.NewIdent(args[3]), + ast.NewIdent(args[4]), + }, + }, + }, + } + case "stroke": + if err := global.Assert(args[1], "int"); err != nil { + return nil, nil, err + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("DrawStroke"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[1]), + }, + }, + }, + } + case "line": + if len(args) < 5 { + return nil, nil, errors.New("expecting at least 5 arguments") + } + + for i := 1; i < 5; i++ { + if err := global.Assert(args[i], "int"); err != nil { + return nil, nil, err + } + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("DrawLine"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[1]), + ast.NewIdent(args[2]), + ast.NewIdent(args[3]), + ast.NewIdent(args[4]), + }, + }, + }, + } + case "rect": + fallthrough + case "lineRect": + if len(args) < 5 { + return nil, nil, errors.New("expecting at least 5 arguments") + } + + for i := 1; i < 5; i++ { + if err := global.Assert(args[i], "int"); err != nil { + return nil, nil, err + } + } + + function := "DrawRect" + if args[0] == "linePoly" { + function = "DrawLineRect" + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent(function), + }, + Args: []ast.Expr{ + ast.NewIdent(args[1]), + ast.NewIdent(args[2]), + ast.NewIdent(args[3]), + ast.NewIdent(args[4]), + }, + }, + }, + } + case "poly": + fallthrough + case "linePoly": + if len(args) < 6 { + return nil, nil, errors.New("expecting at least 6 arguments") + } + + for i := 1; i < 4; i++ { + if err := global.Assert(args[i], "int"); err != nil { + return nil, nil, err + } + } + + for i := 4; i < 6; i++ { + if err := global.Assert(args[i], "float64"); err != nil { + return nil, nil, err + } + } + + function := "DrawPoly" + if args[0] == "linePoly" { + function = "DrawLinePoly" + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent(function), + }, + Args: []ast.Expr{ + ast.NewIdent(args[1]), + ast.NewIdent(args[2]), + ast.NewIdent(args[3]), + ast.NewIdent(args[4]), + ast.NewIdent(args[5]), + }, + }, + }, + } + case "triangle": + if len(args) < 7 { + return nil, nil, errors.New("expecting at least 7 arguments") + } + + for i := 1; i < 7; i++ { + if err := global.Assert(args[i], "int"); err != nil { + return nil, nil, err + } + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("DrawTriangle"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[1]), + ast.NewIdent(args[2]), + ast.NewIdent(args[3]), + ast.NewIdent(args[4]), + ast.NewIdent(args[5]), + ast.NewIdent(args[6]), + }, + }, + }, + } + case "image": + if len(args) < 6 { + return nil, nil, errors.New("expecting at least 6 arguments") + } + + for i := 1; i < 3; i++ { + if err := global.Assert(args[i], "int"); err != nil { + return nil, nil, err + } + } + + image, err := global.Resolve(args[3], "string") + if err != nil { + return nil, nil, err + } + + for i := 4; i < 6; i++ { + if err := global.Assert(args[i], "float64"); err != nil { + return nil, nil, err + } + } + + result = []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("DrawImage"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[1]), + ast.NewIdent(args[2]), + image, + ast.NewIdent(args[4]), + ast.NewIdent(args[5]), + }, + }, + }, + } + default: + return nil, nil, errors.New("unknown control command: " + args[0]) + } + + return result, []string{"github.com/Vilsol/go-mlog/m"}, nil + }, + }) + decompiler.RegisterFuncTranslation("drawflush", decompiler.Translator{ + Translate: func(args []string, global *decompiler.Global) ([]ast.Stmt, []string, error) { + if len(args) != 1 { + return nil, nil, errors.New("expecting 1 argument") + } + + return []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("DrawFlush"), + }, + Args: []ast.Expr{ + ast.NewIdent("\"" + args[0] + "\""), + }, + }, + }, + }, []string{"github.com/Vilsol/go-mlog/m"}, nil + }, + }) +} diff --git a/m/impl/operations.go b/m/impl/operations.go index 33a59c9..7bc0565 100644 --- a/m/impl/operations.go +++ b/m/impl/operations.go @@ -1,8 +1,20 @@ package impl -import "github.com/Vilsol/go-mlog/transpiler" +import ( + "errors" + "github.com/Vilsol/go-mlog/decompiler" + "github.com/Vilsol/go-mlog/transpiler" + "go/ast" + "go/token" + "strconv" +) func init() { + initOperationTranspiler() + initOperationDecompiler() +} + +func initOperationTranspiler() { transpiler.RegisterFuncTranslation("m.Floor", transpiler.Translator{ Count: func(args []transpiler.Resolvable, vars []transpiler.Resolvable) int { return 1 @@ -85,3 +97,299 @@ func init() { }) //op idiv result a b } + +type opDecompiler struct { + ReturnType string + ArgCount int + Token token.Token + Custom func(args []string, global *decompiler.Global) ast.Expr +} + +var opReturnTypes = map[string]opDecompiler{ + "add": { + ReturnType: "float64", + ArgCount: 4, + Token: token.ADD, + }, + "sub": { + ReturnType: "float64", + ArgCount: 4, + Token: token.SUB, + }, + "mul": { + ReturnType: "float64", + ArgCount: 4, + Token: token.MUL, + }, + "div": { + ReturnType: "float64", + ArgCount: 4, + Token: token.QUO, + }, + "idiv": { + ReturnType: "int", + ArgCount: 4, + Custom: func(args []string, global *decompiler.Global) ast.Expr { + return &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("IntDiv"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[2]), + ast.NewIdent(args[3]), + }, + } + }, + }, + "mod": { + ReturnType: "int", + ArgCount: 4, + Token: token.REM, + }, + "pow": { + ReturnType: "float64", + ArgCount: 4, + }, + "equal": { + ReturnType: "bool", + ArgCount: 4, + Token: token.EQL, + }, + "notEqual": { + ReturnType: "bool", + ArgCount: 4, + Token: token.NEQ, + }, + "land": { + ReturnType: "bool", + ArgCount: 4, + Token: token.LAND, + }, + "lessThan": { + ReturnType: "bool", + ArgCount: 4, + Token: token.LSS, + }, + "lessThanEq": { + ReturnType: "bool", + ArgCount: 4, + Token: token.LEQ, + }, + "greaterThan": { + ReturnType: "bool", + ArgCount: 4, + Token: token.GTR, + }, + "greaterThanEq": { + ReturnType: "bool", + ArgCount: 4, + Token: token.GEQ, + }, + "strictEqual": { + ReturnType: "bool", + ArgCount: 4, + Token: token.EQL, + }, + "shl": { + ReturnType: "int", + ArgCount: 4, + Token: token.SHL, + }, + "shr": { + ReturnType: "int", + ArgCount: 4, + Token: token.SHR, + }, + "or": { + ReturnType: "int", + ArgCount: 4, + Token: token.OR, + }, + "and": { + ReturnType: "int", + ArgCount: 4, + Token: token.AND, + }, + "xor": { + ReturnType: "int", + ArgCount: 4, + Token: token.XOR, + }, + "not": { + ReturnType: "int", + ArgCount: 3, + Custom: func(args []string, global *decompiler.Global) ast.Expr { + return &ast.BinaryExpr{ + X: ast.NewIdent(args[2]), + Op: token.XOR, + Y: ast.NewIdent("0xFFFFFFFFFFFFFFFF"), + } + }, + }, + "max": { + ReturnType: "float64", + ArgCount: 4, + }, + "min": { + ReturnType: "float64", + ArgCount: 4, + }, + "angle": { + ReturnType: "float64", + ArgCount: 4, + }, + "len": { + ReturnType: "float64", + ArgCount: 4, + }, + "noise": { + ReturnType: "float64", + ArgCount: 4, + }, + "abs": { + ReturnType: "float64", + ArgCount: 3, + }, + "log": { + ReturnType: "float64", + ArgCount: 3, + }, + "log10": { + ReturnType: "float64", + ArgCount: 3, + Custom: func(args []string, global *decompiler.Global) ast.Expr { + return &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("Log10"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[2]), + }, + } + }, + }, + "floor": { + ReturnType: "int", + ArgCount: 3, + Custom: func(args []string, global *decompiler.Global) ast.Expr { + return &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("Floor"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[2]), + }, + } + }, + }, + "ceil": { + ReturnType: "int", + ArgCount: 3, + Custom: func(args []string, global *decompiler.Global) ast.Expr { + return &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("Ceil"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[2]), + }, + } + }, + }, + "sqrt": { + ReturnType: "float64", + ArgCount: 3, + }, + "rand": { + ReturnType: "float64", + ArgCount: 3, + Custom: func(args []string, global *decompiler.Global) ast.Expr { + return &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("m"), + Sel: ast.NewIdent("Random"), + }, + Args: []ast.Expr{ + ast.NewIdent(args[2]), + }, + } + }, + }, + "sin": { + ReturnType: "float64", + ArgCount: 3, + }, + "cos": { + ReturnType: "float64", + ArgCount: 3, + }, + "tan": { + ReturnType: "float64", + ArgCount: 3, + }, + "asin": { + ReturnType: "float64", + ArgCount: 3, + }, + "acos": { + ReturnType: "float64", + ArgCount: 3, + }, + "atan": { + ReturnType: "float64", + ArgCount: 3, + }, +} + +func initOperationDecompiler() { + decompiler.RegisterFuncTranslation("op", decompiler.Translator{ + Translate: func(args []string, global *decompiler.Global) ([]ast.Stmt, []string, error) { + if len(args) < 2 { + return nil, nil, errors.New("expecting at least 2 arguments") + } + + dec, ok := opReturnTypes[args[0]] + if !ok { + return nil, nil, errors.New("unknown operation: " + args[0]) + } + + if len(args) < dec.ArgCount { + return nil, nil, errors.New("expecting at least " + strconv.Itoa(dec.ArgCount) + " arguments") + } + + tok, err := global.AssignOrDefine(args[1], dec.ReturnType) + if err != nil { + return nil, nil, err + } + + var expr ast.Expr + if dec.Custom != nil { + expr = dec.Custom(args, global) + } else if dec.Token != 0 { + expr = &ast.BinaryExpr{ + X: ast.NewIdent(args[2]), + Op: dec.Token, + Y: ast.NewIdent(args[3]), + } + } else { + return nil, nil, errors.New("unsupported operation: " + args[0]) + } + + return []ast.Stmt{ + &ast.AssignStmt{ + Lhs: []ast.Expr{ + ast.NewIdent(args[1]), + }, + Tok: tok, + Rhs: []ast.Expr{ + expr, + }, + }, + }, []string{"github.com/Vilsol/go-mlog/m"}, nil + }, + }) +} diff --git a/m/impl/types.go b/m/impl/types.go index 4789834..c57e994 100644 --- a/m/impl/types.go +++ b/m/impl/types.go @@ -122,3 +122,8 @@ func genBasicFuncTranslation(constants []string, nArgs int, nVars int) transpile }, nil } } + +const ( + TRUE = "true" + FALSE = "false" +) diff --git a/mlog/tokenizer.go b/mlog/tokenizer.go new file mode 100644 index 0000000..2609b21 --- /dev/null +++ b/mlog/tokenizer.go @@ -0,0 +1,101 @@ +package mlog + +import ( + "strings" +) + +type MLOGLine struct { + Instruction []string + Comment string + SourceLine int + Label string +} + +func Tokenize(input string) ([]MLOGLine, int) { + count := strings.Count(input, "\n") + result := make([]MLOGLine, count+1) + + operationLines := 0 + j := 0 + inString := false + inComment := false + currentLine := make([]string, 0) + var nextLineLabel string + var currentToken strings.Builder + for _, c := range input { + if c == '#' && !inString && !inComment { + if currentToken.Len() > 0 { + currentLine = append(currentLine, currentToken.String()) + } + + currentToken.Reset() + + inComment = true + } else if c == '\n' { + if !inComment && currentToken.Len() > 0 { + currentLine = append(currentLine, currentToken.String()) + } + + if len(currentLine) == 0 && !inComment { + continue + } + + operationLines++ + + result[j] = MLOGLine{ + Instruction: currentLine, + SourceLine: j, + } + + currentLine = make([]string, 0) + + if inComment { + result[j].Comment = currentToken.String() + } + + if nextLineLabel != "" { + result[j].Label = nextLineLabel + nextLineLabel = "" + } + + currentToken.Reset() + inString = false + inComment = false + j++ + } else if (c == ' ' || c == '\t') && !inString && !inComment { + if currentToken.Len() > 0 { + currentLine = append(currentLine, currentToken.String()) + } + currentToken.Reset() + } else if c == '"' { + currentToken.WriteRune(c) + inString = !inString + } else if c == ':' && !inString && !inComment && len(currentLine) == 0 { + nextLineLabel = currentToken.String() + currentToken.Reset() + } else if c == '\r' { + // Ignored + } else { + currentToken.WriteRune(c) + } + } + + if currentToken.Len() != 0 && !inComment { + currentLine = append(currentLine, currentToken.String()) + } + + result[j] = MLOGLine{ + Instruction: currentLine, + SourceLine: j, + } + + if inComment { + result[j].Comment = currentToken.String() + } + + if len(currentLine) > 0 { + operationLines++ + } + + return result, operationLines +} diff --git a/runtime/parser.go b/runtime/parser.go index 3a9f97c..af42a20 100644 --- a/runtime/parser.go +++ b/runtime/parser.go @@ -2,92 +2,13 @@ package runtime import ( "fmt" + "github.com/Vilsol/go-mlog/mlog" "github.com/pkg/errors" "strings" ) -func Tokenize(input string) ([]MLOGLine, int) { - count := strings.Count(input, "\n") - result := make([]MLOGLine, count+1) - - // TODO Parse labels - - operationLines := 0 - j := 0 - inString := false - inComment := false - currentLine := make([]string, 0) - var currentToken strings.Builder - for _, c := range input { - if c == '#' && !inString && !inComment { - if currentToken.Len() > 0 { - currentLine = append(currentLine, currentToken.String()) - } - - currentToken.Reset() - - inComment = true - } else if c == '\n' { - if !inComment { - currentLine = append(currentLine, currentToken.String()) - } - - if len(currentLine) > 0 { - operationLines++ - } - - result[j] = MLOGLine{ - Instruction: currentLine, - SourceLine: j, - } - - currentLine = make([]string, 0) - - if inComment { - result[j].Comment = currentToken.String() - } - - currentToken.Reset() - inString = false - inComment = false - j++ - } else if (c == ' ' || c == '\t') && !inString && !inComment { - if currentToken.Len() > 0 { - currentLine = append(currentLine, currentToken.String()) - } - currentToken.Reset() - } else if c == '"' { - currentToken.WriteRune(c) - inString = !inString - } else if c == '\r' { - // Ignored - } else { - currentToken.WriteRune(c) - } - } - - if currentToken.Len() != 0 { - currentLine = append(currentLine, currentToken.String()) - } - - result[j] = MLOGLine{ - Instruction: currentLine, - SourceLine: j, - } - - if len(currentLine) > 0 { - operationLines++ - } - - if inComment { - result[j].Comment = currentToken.String() - } - - return result, operationLines -} - func Parse(input string) ([]Operation, error) { - lines, operationLines := Tokenize(input) + lines, operationLines := mlog.Tokenize(input) instructions := make([]Operation, operationLines) i := 0 diff --git a/runtime/types.go b/runtime/types.go index 4f681b5..93b97d1 100644 --- a/runtime/types.go +++ b/runtime/types.go @@ -1,6 +1,7 @@ package runtime import ( + "github.com/Vilsol/go-mlog/mlog" "strings" ) @@ -21,14 +22,8 @@ type Variable struct { Constant bool } -type MLOGLine struct { - Instruction []string - Comment string - SourceLine int -} - type Operation struct { - Line MLOGLine + Line mlog.MLOGLine Executor OperationExecutor } diff --git a/tests/runtime/memory_test.go b/tests/runtime/memory_test.go index 1957ac8..d4e0ab9 100644 --- a/tests/runtime/memory_test.go +++ b/tests/runtime/memory_test.go @@ -40,6 +40,7 @@ read m1000 cell1 1000`) context, counter := runtime.ConstructContext(objects) err = runtime.ExecuteContext(operations, context, counter) + testza.AssertNil(t, err) testza.AssertEqual(t, float64(1234), context.Variables["m0"].Value) testza.AssertEqual(t, 1.234, context.Variables["m1"].Value) diff --git a/tests/runtime/operation_test.go b/tests/runtime/operation_test.go index d904152..08a5769 100644 --- a/tests/runtime/operation_test.go +++ b/tests/runtime/operation_test.go @@ -46,7 +46,6 @@ func TestOperationDraw(t *testing.T) { _, err = runtime.Parse(`draw image 1`) testza.AssertNotNil(t, err) testza.AssertEqual(t, "error on line 0: 'draw image 1': expecting at least 6 arguments", err.Error()) - } func TestOperationDrawFlush(t *testing.T) { @@ -204,6 +203,7 @@ set float 1.234`) context, counter := runtime.ConstructContext(map[string]interface{}{}) err = runtime.ExecuteContext(operations, context, counter) + testza.AssertNil(t, err) testza.AssertEqual(t, "hello", context.Variables["str"].Value) testza.AssertEqual(t, int64(1234), context.Variables["int"].Value) @@ -239,6 +239,7 @@ jump 11 strictEqual 1 1`) context, counter := runtime.ConstructContext(map[string]interface{}{}) err = runtime.ExecuteContext(operations, context, counter) + testza.AssertNil(t, err) testza.AssertEqual(t, int64(0), context.Variables["a"].Value) testza.AssertEqual(t, int64(1), context.Variables["b"].Value) diff --git a/tests/runtime/tokenizer_test.go b/tests/runtime/tokenizer_test.go index e50801a..e79295f 100644 --- a/tests/runtime/tokenizer_test.go +++ b/tests/runtime/tokenizer_test.go @@ -2,7 +2,7 @@ package runtime import ( "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/runtime" + "github.com/Vilsol/go-mlog/mlog" "github.com/rs/zerolog" "testing" ) @@ -12,9 +12,9 @@ func init() { } func TestTokenizer(t *testing.T) { - result, _ := runtime.Tokenize("foo \"hello world\" bar # abc\n1234 lorem ipsum") + result, _ := mlog.Tokenize("foo \"hello world\" bar # abc\n1234 lorem ipsum") - testza.AssertEqual(t, []runtime.MLOGLine{ + testza.AssertEqual(t, []mlog.MLOGLine{ {Instruction: []string{"foo", "\"hello world\"", "bar"}, Comment: " abc", SourceLine: 0}, {Instruction: []string{"1234", "lorem", "ipsum"}, Comment: "", SourceLine: 1}, }, result) diff --git a/tests/transpiler/base_test.go b/tests/transpiler/base_test.go index e0c13ed..efbe1b3 100644 --- a/tests/transpiler/base_test.go +++ b/tests/transpiler/base_test.go @@ -1,18 +1,11 @@ package transpiler import ( - "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/transpiler" - "strings" "testing" ) func TestBase(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "Read", input: TestMain(`x := m.Read("cell1", 0) @@ -22,6 +15,11 @@ print _main_x`, }, { name: "Write", + input: TestMain(`m.Write(1.23, "cell1", 0)`, true, false), + output: `write 1.23 cell1 0`, + }, + { + name: "WriteInt", input: TestMain(`m.Write(1, "cell1", 0)`, true, false), output: `write 1 cell1 0`, }, @@ -54,18 +52,5 @@ sensor _main_x _main_b B print _main_x`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } diff --git a/tests/transpiler/constant_test.go b/tests/transpiler/constant_test.go index b9c14f1..b31033d 100644 --- a/tests/transpiler/constant_test.go +++ b/tests/transpiler/constant_test.go @@ -8,11 +8,7 @@ import ( ) func TestConstant(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "Constant", input: `package main diff --git a/tests/transpiler/control_test.go b/tests/transpiler/control_test.go index 2543af8..729a4d4 100644 --- a/tests/transpiler/control_test.go +++ b/tests/transpiler/control_test.go @@ -1,52 +1,31 @@ package transpiler import ( - "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/transpiler" - "strings" "testing" ) func TestControl(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "ControlEnabled", - input: TestMain(`m.ControlEnabled("A", true)`, true, false), - output: `control enabled "A" true`, + input: TestMain(`m.ControlEnabled(m.B("A"), true)`, true, false), + output: `control enabled A true`, }, { name: "ControlShoot", - input: TestMain(`m.ControlShoot("A", 3, 4, true)`, true, false), - output: `control shoot "A" 3 4 true`, + input: TestMain(`m.ControlShoot(m.B("A"), 3, 4, true)`, true, false), + output: `control shoot A 3 4 true`, }, { name: "ControlShootP", - input: TestMain(`m.ControlShootP("A", 5, true)`, true, false), - output: `control shootp "A" 5 true`, + input: TestMain(`m.ControlShootP(m.B("A"), m.B("B"), true)`, true, false), + output: `control shootp A B true`, }, { name: "ControlConfigure", - input: TestMain(`m.ControlConfigure("A", 1)`, true, false), - output: `control configure "A" 1`, + input: TestMain(`m.ControlConfigure(m.B("A"), 1)`, true, false), + output: `control configure A 1`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } diff --git a/tests/transpiler/custom_test.go b/tests/transpiler/custom_test.go index 18c11d6..c48a52f 100644 --- a/tests/transpiler/custom_test.go +++ b/tests/transpiler/custom_test.go @@ -1,18 +1,11 @@ package transpiler import ( - "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/transpiler" - "strings" "testing" ) func TestCustom(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "Const", input: TestMain(`x := m.Const("@copper") @@ -35,19 +28,5 @@ print message1 print 1`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } diff --git a/tests/transpiler/draw_test.go b/tests/transpiler/draw_test.go index 6892b79..691847c 100644 --- a/tests/transpiler/draw_test.go +++ b/tests/transpiler/draw_test.go @@ -1,18 +1,11 @@ package transpiler import ( - "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/transpiler" - "strings" "testing" ) func TestDraw(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "DrawClear", input: TestMain(`m.DrawClear(1, 2, 3)`, true, false), @@ -69,19 +62,5 @@ func TestDraw(t *testing.T) { output: `drawflush display1`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } diff --git a/tests/transpiler/errors_test.go b/tests/transpiler/errors_test.go index 70c2751..c0d4ceb 100644 --- a/tests/transpiler/errors_test.go +++ b/tests/transpiler/errors_test.go @@ -7,11 +7,7 @@ import ( ) func TestErrors(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "InvalidInput", input: `hello world`, diff --git a/tests/transpiler/extra_test.go b/tests/transpiler/extra_test.go index f0065bf..4023941 100644 --- a/tests/transpiler/extra_test.go +++ b/tests/transpiler/extra_test.go @@ -1,18 +1,11 @@ package transpiler import ( - "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/transpiler" - "strings" "testing" ) func TestExtra(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "Sleep", input: TestMain(`x.Sleep(1000)`, false, true), @@ -20,19 +13,5 @@ func TestExtra(t *testing.T) { jump 1 lessThan @time _main_0`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } diff --git a/tests/transpiler/native_test.go b/tests/transpiler/native_test.go index 69f0dc4..c216196 100644 --- a/tests/transpiler/native_test.go +++ b/tests/transpiler/native_test.go @@ -1,18 +1,11 @@ package transpiler import ( - "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/transpiler" - "strings" "testing" ) func TestNative(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "print", input: TestMain(`x := 2 @@ -40,19 +33,5 @@ print(x)`, false, false), print _main_x`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } diff --git a/tests/transpiler/operator_test.go b/tests/transpiler/operator_test.go index 0a30936..7d4c6c1 100644 --- a/tests/transpiler/operator_test.go +++ b/tests/transpiler/operator_test.go @@ -1,18 +1,11 @@ package transpiler import ( - "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/transpiler" - "strings" "testing" ) func TestJumpOperator(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "equal", input: TestMain(`if 1 == 2 { print(1) }`, false, false), @@ -62,29 +55,11 @@ jump 4 always print 1`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } func TestNormalOperator(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "add", input: TestMain(`x := 1 + 2 @@ -231,29 +206,11 @@ print(x)`, false, false), print _main_x`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } func TestFunctionOperator(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "Floor", input: TestMain(`x := m.Floor(1.2) @@ -283,19 +240,5 @@ print(x)`, true, false), print _main_x`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } diff --git a/tests/transpiler/stacked_function_test.go b/tests/transpiler/stacked_function_test.go index 08b83e2..235f48a 100644 --- a/tests/transpiler/stacked_function_test.go +++ b/tests/transpiler/stacked_function_test.go @@ -8,11 +8,7 @@ import ( ) func TestStackedFunction(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "FunctionDynamicReturn", input: `package main diff --git a/tests/transpiler/stackless_function_test.go b/tests/transpiler/stackless_function_test.go index 1ddcffe..246d808 100644 --- a/tests/transpiler/stackless_function_test.go +++ b/tests/transpiler/stackless_function_test.go @@ -8,11 +8,7 @@ import ( ) func TestStacklessFunction(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "FunctionDynamicReturn", input: `package main diff --git a/tests/transpiler/statement_test.go b/tests/transpiler/statement_test.go index 14b11f5..a88c363 100644 --- a/tests/transpiler/statement_test.go +++ b/tests/transpiler/statement_test.go @@ -1,18 +1,11 @@ package transpiler import ( - "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/transpiler" - "strings" "testing" ) func TestStatement(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "IfElseifElse", input: TestMain(`if x := 1; x == 2 { @@ -186,20 +179,41 @@ jump 5 always end print _main_x`, }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) + { + name: "Labels", + input: TestMain(`print(1) + goto loop - if err != nil { - t.Error(err) - return - } +test: + print(2) + goto end + +loop: + for i := 0; i < 10; i++ { + print(3) + } - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) + print(4) + goto test +end: + print(5)`, false, false), + output: `print 1 +jump loop +test: +print 2 +jump end +loop: +set _main_i 0 +jump 7 lessThan _main_i 10 +jump 10 always +print 3 +op add _main_i _main_i 1 +jump 7 lessThan _main_i 10 +print 4 +jump test +end: +print 5`, + }, } + RunTests(t, tests) } diff --git a/tests/transpiler/type_test.go b/tests/transpiler/type_test.go index 152b7c9..69a02c8 100644 --- a/tests/transpiler/type_test.go +++ b/tests/transpiler/type_test.go @@ -1,18 +1,11 @@ package transpiler import ( - "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/transpiler" - "strings" "testing" ) func TestType(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "Radar_This", input: TestMain(`x := m.Radar(m.This, m.RTAlly, m.RTEnemy, m.RTBoss, false, m.RSArmor) @@ -30,19 +23,5 @@ sensor _main_x _main_b @health print _main_x`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } diff --git a/tests/transpiler/unit_control_test.go b/tests/transpiler/unit_control_test.go index 4a2b5e9..96fbb16 100644 --- a/tests/transpiler/unit_control_test.go +++ b/tests/transpiler/unit_control_test.go @@ -1,18 +1,11 @@ package transpiler import ( - "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/transpiler" - "strings" "testing" ) func TestUnitControl(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "UnitStop", input: TestMain(`m.UnitStop()`, true, false), @@ -106,19 +99,5 @@ print(x)`, true, false), print _main_x`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } diff --git a/tests/transpiler/unit_test.go b/tests/transpiler/unit_test.go index bf4d9ba..db5749e 100644 --- a/tests/transpiler/unit_test.go +++ b/tests/transpiler/unit_test.go @@ -1,18 +1,11 @@ package transpiler import ( - "github.com/MarvinJWendt/testza" - "github.com/Vilsol/go-mlog/transpiler" - "strings" "testing" ) func TestUnit(t *testing.T) { - tests := []struct { - name string - input string - output string - }{ + tests := []Test{ { name: "UnitBind", input: TestMain(`m.UnitBind("A")`, true, false), @@ -76,19 +69,5 @@ print _main_z print _main_b`, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ - NoStartup: true, - }) - - if err != nil { - t.Error(err) - return - } - - test.output = test.output + "\nend" - testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) - }) - } + RunTests(t, tests) } diff --git a/tests/transpiler/utils.go b/tests/transpiler/utils.go index d28b286..ceeb8e1 100644 --- a/tests/transpiler/utils.go +++ b/tests/transpiler/utils.go @@ -2,12 +2,22 @@ package transpiler import ( "fmt" + "github.com/MarvinJWendt/testza" _ "github.com/Vilsol/go-mlog/m" _ "github.com/Vilsol/go-mlog/m/impl" + "github.com/Vilsol/go-mlog/transpiler" _ "github.com/Vilsol/go-mlog/x" _ "github.com/Vilsol/go-mlog/x/impl" + "strings" + "testing" ) +type Test struct { + name string + input string + output string +} + func TestMain(main string, useM bool, useX bool) string { result := "package main\n\n" @@ -28,3 +38,21 @@ func main() { %s }`, result, main) } + +func RunTests(t *testing.T, tests []Test) { + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ + NoStartup: true, + }) + + if err != nil { + t.Error(err) + return + } + + test.output = test.output + "\nend" + testza.AssertEqual(t, test.output, strings.Trim(mlog, "\n")) + }) + } +} diff --git a/transpiler/main.go b/transpiler/main.go index bb94b48..89ce69e 100644 --- a/transpiler/main.go +++ b/transpiler/main.go @@ -296,7 +296,9 @@ func GolangToMLOG(input string, options Options) (string, error) { } for _, statement := range fn.Statements { - position += statement.SetPosition(position) + if statement.Size() > 0 { + position += statement.SetPosition(position) + } } } @@ -358,7 +360,9 @@ func GolangToMLOG(input string, options Options) (string, error) { outputData += line[0] + "\n" } } - lineNumber += len(statements) + if statement.Size() > 0 { + lineNumber += len(statements) + } } for _, fn := range global.Functions { @@ -382,7 +386,9 @@ func GolangToMLOG(input string, options Options) (string, error) { outputData += line[0] + "\n" } } - lineNumber += len(statements) + if statement.Size() > 0 { + lineNumber += len(statements) + } } } diff --git a/transpiler/statement.go b/transpiler/statement.go index 63d8739..c1dd3f7 100644 --- a/transpiler/statement.go +++ b/transpiler/statement.go @@ -30,6 +30,8 @@ func statementToMLOG(ctx context.Context, statement ast.Stmt) ([]MLOGStatement, return branchStmtToMLOG(subCtx, castStmt) case *ast.SwitchStmt: return switchStmtToMLOG(subCtx, castStmt) + case *ast.LabeledStmt: + return labeledStmtToMLOG(subCtx, castStmt) } return nil, Err(subCtx, fmt.Sprintf("statement type not supported: %T", statement)) @@ -408,6 +410,19 @@ func branchStmtToMLOG(ctx context.Context, statement *ast.BranchStmt) ([]MLOGSta case token.FALLTHROUGH: // Requires no extra instructions return []MLOGStatement{}, nil + case token.GOTO: + return []MLOGStatement{ + &MLOG{ + Statement: [][]Resolvable{ + { + &Value{Value: "jump"}, + &Value{Value: statement.Label.Name}, + }, + }, + Comment: "Jump to label", + SourcePos: statement, + }, + }, nil } return nil, Err(ctx, fmt.Sprintf("branch statement not supported: %s", statement.Tok)) @@ -519,3 +534,19 @@ func switchStmtToMLOG(ctx context.Context, statement *ast.SwitchStmt) ([]MLOGSta return append(results, combined...), nil } + +func labeledStmtToMLOG(ctx context.Context, statement *ast.LabeledStmt) ([]MLOGStatement, error) { + subStmt, err := statementToMLOG(ctx, statement.Stmt) + if err != nil { + return nil, err + } + + return append([]MLOGStatement{ + &MLOGLabel{ + MLOG: MLOG{ + SourcePos: statement, + }, + Name: statement.Label.Name, + }, + }, subStmt...), nil +} diff --git a/transpiler/type_label.go b/transpiler/type_label.go new file mode 100644 index 0000000..9fa83a0 --- /dev/null +++ b/transpiler/type_label.go @@ -0,0 +1,22 @@ +package transpiler + +type MLOGLabel struct { + MLOG + Name string +} + +func (m *MLOGLabel) ToMLOG() [][]Resolvable { + return [][]Resolvable{ + { + &Value{Value: m.Name + ":"}, + }, + } +} + +func (m *MLOGLabel) Size() int { + return 0 +} + +func (m *MLOGLabel) GetComment(int) string { + return "Add a label" +} diff --git a/transpiler/type_mlog.go b/transpiler/type_mlog.go index 0b923ac..2db8590 100644 --- a/transpiler/type_mlog.go +++ b/transpiler/type_mlog.go @@ -181,6 +181,10 @@ func (m *MLOGTrampoline) ToMLOG() [][]Resolvable { } } +func (m *MLOGTrampoline) Size() int { + return 1 +} + func (m *MLOGTrampoline) GetComment(int) string { return "Set Trampoline Address" } diff --git a/transpiler/type_native.go b/transpiler/type_native.go index 6e39734..92ca8aa 100644 --- a/transpiler/type_native.go +++ b/transpiler/type_native.go @@ -23,6 +23,10 @@ func (m *MLOGStackWriter) ToMLOG() [][]Resolvable { } } +func (m *MLOGStackWriter) Size() int { + return 1 +} + func (m *MLOGStackWriter) GetComment(int) string { return "Update Stack Pointer" } diff --git a/transpiler/types.go b/transpiler/types.go index 754e143..62a37f5 100644 --- a/transpiler/types.go +++ b/transpiler/types.go @@ -3,6 +3,7 @@ package transpiler import ( "context" "strconv" + "strings" ) func MLOGToString(ctx context.Context, statements [][]Resolvable, statement MLOGAble, lineNumber int, source string) [][]string { @@ -31,7 +32,7 @@ func MLOGToString(ctx context.Context, statements [][]Resolvable, statement MLOG if ctx.Value(contextOptions).(Options).Source { sourcePos := statement.GetSourcePos(lineNumber) if sourcePos != nil { - currentLine = append(currentLine, "# "+source[sourcePos.Pos()-1:sourcePos.End()-1]) + currentLine = append(currentLine, "# "+strings.ReplaceAll(source[sourcePos.Pos()-1:sourcePos.End()-1], "\n", "")) } else { currentLine = append(currentLine, "") } diff --git a/wasm/main.go b/wasm/main.go index c70f12b..197ace5 100644 --- a/wasm/main.go +++ b/wasm/main.go @@ -1,7 +1,9 @@ package main import ( + "encoding/json" "fmt" + "github.com/Vilsol/go-mlog/checker" _ "github.com/Vilsol/go-mlog/m" _ "github.com/Vilsol/go-mlog/m/impl" "github.com/Vilsol/go-mlog/transpiler" @@ -13,10 +15,13 @@ import ( func transpileWrapper() js.Func { transpileFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} { - if len(args) != 1 { + if len(args) != 4 { return "Invalid no of arguments passed" } input := args[0].String() + numbers := args[1].Bool() + comments := args[2].Bool() + source := args[3].Bool() defer func() { if r := recover(); r != nil { @@ -26,8 +31,9 @@ func transpileWrapper() js.Func { }() mlog, err := transpiler.GolangToMLOG(input, transpiler.Options{ - Numbers: false, - Comments: false, + Numbers: numbers, + Comments: comments, + Source: source, }) if err != nil { fmt.Printf("error transpiling: %s\n", err) @@ -40,6 +46,15 @@ func transpileWrapper() js.Func { func main() { fmt.Println("Transpiler Initialized") + js.Global().Set("transpileGo", transpileWrapper()) - <-make(chan bool) + + result := checker.GetSerializablePackages() + marshal, _ := json.Marshal(result) + + var processed map[string]interface{} + _ = json.Unmarshal(marshal, &processed) + + js.Global().Set("goTypings", processed) + select {} }