diff --git a/README.md b/README.md index 4d8ba0c..14c0912 100644 --- a/README.md +++ b/README.md @@ -20,18 +20,16 @@ A Web IDE is available [here](https://vilsol.github.io/go-mlog-web/?1) * `return` from functions * `for` loops * `if`/`else if`/`else` statements +* `switch` statement +* `break`/`continue`/`fallthrough` statements * Binary and Unary math * Function level variable scopes +* Contextual errors ## Roadmap -* Documentation -* `break` out of loops -* `switch` statement * Full variable block scoping * `print`/`println` multiple arguments -* Errors with context -* Extensions * Variable argument count functions * Multiple function return values * Optimize simple jump instructions diff --git a/go.mod b/go.mod index 286fe1a..3ef35c0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/Vilsol/go-mlog go 1.15 require ( + github.com/davecgh/go-spew v1.1.1 github.com/sirupsen/logrus v1.7.0 github.com/spf13/cobra v1.1.1 github.com/spf13/viper v1.7.1 diff --git a/tests/errors_test.go b/tests/errors_test.go index 8a6217f..97ca9d9 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -26,13 +26,13 @@ func TestErrors(t *testing.T) { name: "NoExternalImports", input: `package main import "time"`, - output: `unregistered import used: "time"`, + output: `error at 21: unregistered import used: "time"`, }, { name: "GlobalScopeVariable", input: `package main var x = 1`, - output: `global scope may only contain constants not variables`, + output: `error at 14: global scope may only contain constants not variables`, }, { name: "NoMainFunction", @@ -42,42 +42,37 @@ var x = 1`, { name: "InvalidOperator", input: TestMain(`x := 1 &^ 1`), - output: `operator statement cannot use this operation: &^`, + output: `error at 103: operator statement cannot use this operation: &^`, }, { name: "NotSupportSelect", input: TestMain(`select {}`), - output: `statement type not supported: *ast.SelectStmt`, - }, - { - name: "NotSupportSwitch", - input: TestMain(`switch {}`), - output: `statement type not supported: *ast.SwitchStmt`, + output: `error at 103: statement type not supported: *ast.SelectStmt`, }, { name: "NotSupportGo", input: TestMain(`go foo()`), - output: `statement type not supported: *ast.GoStmt`, + output: `error at 103: statement type not supported: *ast.GoStmt`, }, { name: "NotSupportSend", input: TestMain(`foo <- 1`), - output: `statement type not supported: *ast.SendStmt`, + output: `error at 103: statement type not supported: *ast.SendStmt`, }, { name: "NotSupportDefer", input: TestMain(`defer func() {}()`), - output: `statement type not supported: *ast.DeferStmt`, + output: `error at 103: statement type not supported: *ast.DeferStmt`, }, { name: "NotSupportRange", input: TestMain(`for i := range nums {}`), - output: `statement type not supported: *ast.RangeStmt`, + output: `error at 103: statement type not supported: *ast.RangeStmt`, }, { name: "InvalidAssignment", input: TestMain(`1 = 2`), - output: `left side variable assignment can only contain identifications`, + output: `error at 103: left side variable assignment can only contain identifications`, }, { name: "InvalidParamTypeString", @@ -90,7 +85,7 @@ func main() { func sample1(arg string) int { return 1 }`, - output: `function parameters may only be integers or floating point numbers`, + output: `error at 57: function parameters may only be integers or floating point numbers`, }, { name: "InvalidParamTypeOther", @@ -103,12 +98,12 @@ func main() { func sample1(arg hello.world) int { return 1 }`, - output: `function parameters may only be integers or floating point numbers`, + output: `error at 53: function parameters may only be integers or floating point numbers`, }, { name: "CallToUnknownFunction", input: TestMain(`foo()`), - output: `unknown function: foo`, + output: `error at 89: unknown function: foo`, }, { name: "InvalidConstant", @@ -118,7 +113,7 @@ const x = 1 + 2 func main() { }`, - output: `unknown constant type: *ast.BinaryExpr`, + output: `error at 21: unknown constant type: *ast.BinaryExpr`, }, { name: "EmptyPrintlnError", @@ -151,7 +146,7 @@ func main() { func sample() (int, int) { return 1, 2 }`, - output: `only single value returns are supported`, + output: `error at 70: only single value returns are supported`, }, } for _, test := range tests { diff --git a/tests/options_test.go b/tests/options_test.go index 9864759..1bae069 100644 --- a/tests/options_test.go +++ b/tests/options_test.go @@ -40,17 +40,18 @@ func foo(x int) int { 5: set @return _foo_1 6: read @counter bank1 @stack 7: set _main_i 0 - 8: op add @stack @stack 1 - 9: write _main_i bank1 @stack - 10: op add @stack @stack 1 - 11: write 13 bank1 @stack - 12: jump 2 always - 13: op sub @stack @stack 2 - 14: set _main_0 @return - 15: print _main_0 - 16: print "\n" - 17: op add _main_i _main_i 1 - 18: jump 8 lessThan _main_i 10`, + 8: jump 20 lessThan _main_i 10 + 9: op add @stack @stack 1 + 10: write _main_i bank1 @stack + 11: op add @stack @stack 1 + 12: write 14 bank1 @stack + 13: jump 2 always + 14: op sub @stack @stack 2 + 15: set _main_0 @return + 16: print _main_0 + 17: print "\n" + 18: op add _main_i _main_i 1 + 19: jump 9 lessThan _main_i 10`, }, { name: "Debug", @@ -80,6 +81,9 @@ write @stack cell2 1 set _main_i 0 write @counter cell2 0 write @stack cell2 1 +jump 54 lessThan _main_i 10 +write @counter cell2 0 +write @stack cell2 1 op add @stack @stack 1 write @counter cell2 0 write @stack cell2 1 @@ -89,7 +93,7 @@ write @stack cell2 1 op add @stack @stack 1 write @counter cell2 0 write @stack cell2 1 -write 35 bank1 @stack +write 38 bank1 @stack write @counter cell2 0 write @stack cell2 1 jump 4 always @@ -108,7 +112,7 @@ write @stack cell2 1 op add _main_i _main_i 1 write @counter cell2 0 write @stack cell2 1 -jump 22 lessThan _main_i 10`, +jump 25 lessThan _main_i 10`, }, { name: "Comments", @@ -128,17 +132,18 @@ read @counter bank1 @stack // Trampoline back // Function: main // set _main_i 0 // Set the variable to the value +jump 20 lessThan _main_i 10 // Jump to end of loop op add @stack @stack 1 // Update Stack Pointer write _main_i bank1 @stack // Write argument to memory op add @stack @stack 1 // Update Stack Pointer -write 13 bank1 @stack // Set Trampoline Address +write 14 bank1 @stack // Set Trampoline Address jump 2 always // Jump to function: foo op sub @stack @stack 2 // Update Stack Pointer set _main_0 @return // Set the variable to the value print _main_0 // Call to native function print "\n" // Call to native function op add _main_i _main_i 1 // Execute for loop post condition increment/decrement -jump 8 lessThan _main_i 10 // Jump to start of loop`, +jump 9 lessThan _main_i 10 // Jump to start of loop`, }, { name: "All", @@ -146,63 +151,31 @@ jump 8 lessThan _main_i 10 // Jump to start of loop`, options: transpiler.Options{ Numbers: true, Comments: true, - Debug: true, }, output: ` 0: set @stack 0 // Reset Stack - 1: jump 19 always // Jump to start of main + 1: jump 7 always // Jump to start of main // Function: foo // - 2: write @counter cell2 0 // Debug - 3: write @stack cell2 1 // Debug - 4: op sub _foo_0 @stack 1 // Calculate address of parameter - 5: write @counter cell2 0 // Debug - 6: write @stack cell2 1 // Debug - 7: read _foo_x bank1 _foo_0 // Read parameter into variable - 8: write @counter cell2 0 // Debug - 9: write @stack cell2 1 // Debug - 10: op add _foo_1 _foo_x 20 // Execute operation - 11: write @counter cell2 0 // Debug - 12: write @stack cell2 1 // Debug - 13: set @return _foo_1 // Set return data - 14: write @counter cell2 0 // Debug - 15: write @stack cell2 1 // Debug - 16: read @counter bank1 @stack // Trampoline back + 2: op sub _foo_0 @stack 1 // Calculate address of parameter + 3: read _foo_x bank1 _foo_0 // Read parameter into variable + 4: op add _foo_1 _foo_x 20 // Execute operation + 5: set @return _foo_1 // Set return data + 6: read @counter bank1 @stack // Trampoline back // Function: main // - 17: write @counter cell2 0 // Debug - 18: write @stack cell2 1 // Debug - 19: set _main_i 0 // Set the variable to the value - 20: write @counter cell2 0 // Debug - 21: write @stack cell2 1 // Debug - 22: op add @stack @stack 1 // Update Stack Pointer - 23: write @counter cell2 0 // Debug - 24: write @stack cell2 1 // Debug - 25: write _main_i bank1 @stack // Write argument to memory - 26: write @counter cell2 0 // Debug - 27: write @stack cell2 1 // Debug - 28: op add @stack @stack 1 // Update Stack Pointer - 29: write @counter cell2 0 // Debug - 30: write @stack cell2 1 // Debug - 31: write 35 bank1 @stack // Set Trampoline Address - 32: write @counter cell2 0 // Debug - 33: write @stack cell2 1 // Debug - 34: jump 4 always // Jump to function: foo - 35: write @counter cell2 0 // Debug - 36: write @stack cell2 1 // Debug - 37: op sub @stack @stack 2 // Update Stack Pointer - 38: write @counter cell2 0 // Debug - 39: write @stack cell2 1 // Debug - 40: set _main_0 @return // Set the variable to the value - 41: write @counter cell2 0 // Debug - 42: write @stack cell2 1 // Debug - 43: print _main_0 // Call to native function - 44: print "\n" // Call to native function - 45: write @counter cell2 0 // Debug - 46: write @stack cell2 1 // Debug - 47: op add _main_i _main_i 1 // Execute for loop post condition increment/decrement - 48: write @counter cell2 0 // Debug - 49: write @stack cell2 1 // Debug - 50: jump 22 lessThan _main_i 10 // Jump to start of loop`, + 7: set _main_i 0 // Set the variable to the value + 8: jump 20 lessThan _main_i 10 // Jump to end of loop + 9: op add @stack @stack 1 // Update Stack Pointer + 10: write _main_i bank1 @stack // Write argument to memory + 11: op add @stack @stack 1 // Update Stack Pointer + 12: write 14 bank1 @stack // Set Trampoline Address + 13: jump 2 always // Jump to function: foo + 14: op sub @stack @stack 2 // Update Stack Pointer + 15: set _main_0 @return // Set the variable to the value + 16: print _main_0 // Call to native function + 17: print "\n" // Call to native function + 18: op add _main_i _main_i 1 // Execute for loop post condition increment/decrement + 19: jump 9 lessThan _main_i 10 // Jump to start of loop`, }, } for _, test := range tests { diff --git a/tests/statement_test.go b/tests/statement_test.go index 44d5a15..45bec3d 100644 --- a/tests/statement_test.go +++ b/tests/statement_test.go @@ -39,9 +39,10 @@ print 6`, name: "ForLoop", input: TestMain(`for i := 0; i < 10; i++ { print(i) }`), output: `set _main_i 0 +jump 5 lessThan _main_i 10 print _main_i op add _main_i _main_i 1 -jump 1 lessThan _main_i 10`, +jump 2 lessThan _main_i 10`, }, { name: "Reassignment", @@ -58,6 +59,86 @@ jump 1 lessThan _main_i 10`, input: TestMain(`x := 'A'`), output: `set _main_x "A"`, }, + { + name: "Break", + input: TestMain(`for i := 0; i < 10; i++ { if i == 5 { break; }; println(i); }`), + output: `set _main_i 0 +jump 10 lessThan _main_i 10 +op equal _main_0 _main_i 5 +jump 5 equal _main_0 1 +jump 6 always +jump 10 always +print _main_i +print "\n" +op add _main_i _main_i 1 +jump 2 lessThan _main_i 10`, + }, + { + name: "Continue", + input: TestMain(`for i := 0; i < 10; i++ { if i == 5 { continue; }; println(i); }`), + output: `set _main_i 0 +jump 10 lessThan _main_i 10 +op equal _main_0 _main_i 5 +jump 5 equal _main_0 1 +jump 6 always +jump 8 always +print _main_i +print "\n" +op add _main_i _main_i 1 +jump 2 lessThan _main_i 10`, + }, + { + name: "Switch", + input: TestMain(`switch 10 { +case 0: + println("0") +case 1: + println("1") + fallthrough +case 2: + println("2") + fallthrough +case 3, 4: + println("3, 4") + break +case 5, 6: + println("5, 6") + break +default: + println("default") + break +}`), + output: `jump 2 equal 10 0 +jump 5 always +print "0" +print "\n" +jump 30 always +jump 7 equal 10 1 +jump 10 always +print "1" +print "\n" +jump 12 always +jump 12 equal 10 2 +jump 15 always +print "2" +print "\n" +jump 18 always +jump 18 equal 10 3 +jump 18 equal 10 4 +jump 21 always +print "3, 4" +print "\n" +jump 30 always +jump 24 equal 10 5 +jump 24 equal 10 6 +jump 27 always +print "5, 6" +print "\n" +jump 30 always +print "default" +print "\n" +jump 30 always`, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/transpiler/context.go b/transpiler/context.go new file mode 100644 index 0000000..be3644b --- /dev/null +++ b/transpiler/context.go @@ -0,0 +1,17 @@ +package transpiler + +const ( + contextOptions = "options" + contextFunction = "function" + contextStatement = "statement" + contextSpec = "spec" + contextDecl = "decl" + contextBlock = "block" + contextBreakableBlock = "breakableBlock" + contextSwitchClauseBlock = "switchClauseBlock" +) + +type ContextBlock struct { + Statements []MLOGStatement + Extra []MLOGStatement +} diff --git a/transpiler/error.go b/transpiler/error.go new file mode 100644 index 0000000..2b013fb --- /dev/null +++ b/transpiler/error.go @@ -0,0 +1,35 @@ +package transpiler + +import ( + "context" + "errors" + "fmt" + "go/ast" +) + +type ContextualError struct { + error + Context context.Context +} + +func (e ContextualError) Error() string { + if e.Context != nil { + if stmt, ok := e.Context.Value(contextStatement).(ast.Stmt); ok { + return fmt.Sprintf("error at %d: %s", stmt.Pos(), e.error.Error()) + } else if fn, ok := e.Context.Value(contextFunction).(*ast.FuncDecl); ok { + return fmt.Sprintf("error at %d: %s", fn.Pos(), e.error.Error()) + } else if spec, ok := e.Context.Value(contextSpec).(ast.Spec); ok { + return fmt.Sprintf("error at %d: %s", spec.Pos(), e.error.Error()) + } else if decl, ok := e.Context.Value(contextDecl).(ast.Decl); ok { + return fmt.Sprintf("error at %d: %s", decl.Pos(), e.error.Error()) + } + } + return e.error.Error() +} + +func Err(ctx context.Context, err string) ContextualError { + return ContextualError{ + error: errors.New(err), + Context: ctx, + } +} diff --git a/transpiler/expression.go b/transpiler/expression.go index c3b9c5d..061cbde 100644 --- a/transpiler/expression.go +++ b/transpiler/expression.go @@ -1,20 +1,18 @@ package transpiler import ( - "errors" + "context" "fmt" "go/ast" "go/token" "strings" ) -func expressionToMLOG(ident []Resolvable, expr ast.Expr, options Options) ([]MLOGStatement, error) { - switch expr.(type) { +func expressionToMLOG(ctx context.Context, ident []Resolvable, expr ast.Expr) ([]MLOGStatement, error) { + switch castExpr := expr.(type) { case *ast.BasicLit: - basicExpr := expr.(*ast.BasicLit) - - value := basicExpr.Value - if basicExpr.Kind == token.CHAR { + value := castExpr.Value + if castExpr.Kind == token.CHAR { value = "\"" + strings.Trim(value, "'") + "\"" } @@ -29,16 +27,14 @@ func expressionToMLOG(ident []Resolvable, expr ast.Expr, options Options) ([]MLO }, }}, nil case *ast.Ident: - identExpr := expr.(*ast.Ident) - - if identExpr.Name == "true" || identExpr.Name == "false" { + if castExpr.Name == "true" || castExpr.Name == "false" { return []MLOGStatement{&MLOG{ Comment: "Set the variable to the value", Statement: [][]Resolvable{ { &Value{Value: "set"}, ident[0], - &Value{Value: identExpr.Name}, + &Value{Value: castExpr.Name}, }, }, }}, nil @@ -50,26 +46,25 @@ func expressionToMLOG(ident []Resolvable, expr ast.Expr, options Options) ([]MLO { &Value{Value: "set"}, ident[0], - &NormalVariable{Name: identExpr.Name}, + &NormalVariable{Name: castExpr.Name}, }, }, }}, nil case *ast.BinaryExpr: - binaryExpr := expr.(*ast.BinaryExpr) - - if opTranslated, ok := regularOperators[binaryExpr.Op]; ok { + if opTranslated, ok := regularOperators[castExpr.Op]; ok { instructions := make([]MLOGStatement, 0) var leftSide Resolvable var rightSide Resolvable - if basicLit, ok := binaryExpr.X.(*ast.BasicLit); ok { + // TODO Convert to switch + if basicLit, ok := castExpr.X.(*ast.BasicLit); ok { leftSide = &Value{Value: basicLit.Value} - } else if leftIdent, ok := binaryExpr.X.(*ast.Ident); ok { + } else if leftIdent, ok := castExpr.X.(*ast.Ident); ok { leftSide = &NormalVariable{Name: leftIdent.Name} - } else if leftExpr, ok := binaryExpr.X.(ast.Expr); ok { + } else if leftExpr, ok := castExpr.X.(ast.Expr); ok { dVar := &DynamicVariable{} - exprInstructions, err := expressionToMLOG([]Resolvable{dVar}, leftExpr, options) + exprInstructions, err := expressionToMLOG(ctx, []Resolvable{dVar}, leftExpr) if err != nil { return nil, err } @@ -77,17 +72,18 @@ func expressionToMLOG(ident []Resolvable, expr ast.Expr, options Options) ([]MLO instructions = append(instructions, exprInstructions...) leftSide = dVar } else { - return nil, errors.New(fmt.Sprintf("unknown left side expression type: %T", binaryExpr.X)) + return nil, Err(ctx, fmt.Sprintf("unknown left side expression type: %T", castExpr.X)) } - if basicLit, ok := binaryExpr.Y.(*ast.BasicLit); ok { + // TODO Convert to switch + if basicLit, ok := castExpr.Y.(*ast.BasicLit); ok { rightSide = &Value{Value: basicLit.Value} - } else if rightIdent, ok := binaryExpr.Y.(*ast.Ident); ok { + } else if rightIdent, ok := castExpr.Y.(*ast.Ident); ok { rightSide = &NormalVariable{Name: rightIdent.Name} - } else if rightExpr, ok := binaryExpr.Y.(ast.Expr); ok { + } else if rightExpr, ok := castExpr.Y.(ast.Expr); ok { dVar := &DynamicVariable{} - exprInstructions, err := expressionToMLOG([]Resolvable{dVar}, rightExpr, options) + exprInstructions, err := expressionToMLOG(ctx, []Resolvable{dVar}, rightExpr) if err != nil { return nil, err } @@ -95,7 +91,7 @@ func expressionToMLOG(ident []Resolvable, expr ast.Expr, options Options) ([]MLO instructions = append(instructions, exprInstructions...) rightSide = dVar } else { - return nil, errors.New(fmt.Sprintf("unknown right side expression type: %T", binaryExpr.Y)) + return nil, Err(ctx, fmt.Sprintf("unknown right side expression type: %T", castExpr.Y)) } return append(instructions, &MLOG{ @@ -111,29 +107,28 @@ func expressionToMLOG(ident []Resolvable, expr ast.Expr, options Options) ([]MLO }, }), nil } else { - return nil, errors.New(fmt.Sprintf("operator statement cannot use this operation: %s", binaryExpr.Op.String())) + return nil, Err(ctx, fmt.Sprintf("operator statement cannot use this operation: %s", castExpr.Op.String())) } case *ast.CallExpr: - callInstructions, err := callExprToMLOG(expr.(*ast.CallExpr), ident, options) + callInstructions, err := callExprToMLOG(ctx, castExpr, ident) if err != nil { return nil, err } return callInstructions, err case *ast.UnaryExpr: - unaryExpr := expr.(*ast.UnaryExpr) - - if _, ok := regularOperators[unaryExpr.Op]; ok { + if _, ok := regularOperators[castExpr.Op]; ok { instructions := make([]MLOGStatement, 0) var x Resolvable - if basicLit, ok := unaryExpr.X.(*ast.BasicLit); ok { + // TODO Convert to switch + if basicLit, ok := castExpr.X.(*ast.BasicLit); ok { x = &Value{Value: basicLit.Value} - } else if leftIdent, ok := unaryExpr.X.(*ast.Ident); ok { + } else if leftIdent, ok := castExpr.X.(*ast.Ident); ok { x = &NormalVariable{Name: leftIdent.Name} - } else if leftExpr, ok := unaryExpr.X.(ast.Expr); ok { + } else if leftExpr, ok := castExpr.X.(ast.Expr); ok { dVar := &DynamicVariable{} - exprInstructions, err := expressionToMLOG([]Resolvable{dVar}, leftExpr, options) + exprInstructions, err := expressionToMLOG(ctx, []Resolvable{dVar}, leftExpr) if err != nil { return nil, err } @@ -141,11 +136,11 @@ func expressionToMLOG(ident []Resolvable, expr ast.Expr, options Options) ([]MLO instructions = append(instructions, exprInstructions...) x = dVar } else { - return nil, errors.New(fmt.Sprintf("unknown unary expression type: %T", unaryExpr.X)) + return nil, Err(ctx, fmt.Sprintf("unknown unary expression type: %T", castExpr.X)) } var statement []Resolvable - switch unaryExpr.Op { + switch castExpr.Op { case token.NOT: statement = []Resolvable{ &Value{Value: "op"}, @@ -167,7 +162,7 @@ func expressionToMLOG(ident []Resolvable, expr ast.Expr, options Options) ([]MLO } if statement == nil { - return nil, errors.New(fmt.Sprintf("unsupported unary operation: %s", unaryExpr.Op.String())) + return nil, Err(ctx, fmt.Sprintf("unsupported unary operation: %s", castExpr.Op.String())) } return append(instructions, &MLOG{ @@ -175,26 +170,25 @@ func expressionToMLOG(ident []Resolvable, expr ast.Expr, options Options) ([]MLO Statement: [][]Resolvable{statement}, }), nil } else { - return nil, errors.New(fmt.Sprintf("operator statement cannot use this operation: %s", unaryExpr.Op.String())) + return nil, Err(ctx, fmt.Sprintf("operator statement cannot use this operation: %s", castExpr.Op.String())) } case *ast.ParenExpr: - parenExpr := expr.(*ast.ParenExpr) - instructions, err := expressionToMLOG(ident, parenExpr.X, options) + instructions, err := expressionToMLOG(ctx, ident, castExpr.X) if err != nil { return nil, err } return instructions, nil case *ast.SelectorExpr: - mlog, _, err := selectorExprToMLOG(ident[0], expr.(*ast.SelectorExpr)) + mlog, _, err := selectorExprToMLOG(ctx, ident[0], castExpr) return mlog, err default: - return nil, errors.New(fmt.Sprintf("unsupported expression type: %T", expr)) + return nil, Err(ctx, fmt.Sprintf("unsupported expression type: %T", expr)) } } -func selectorExprToMLOG(ident Resolvable, selectorExpr *ast.SelectorExpr) ([]MLOGStatement, string, error) { +func selectorExprToMLOG(ctx context.Context, ident Resolvable, selectorExpr *ast.SelectorExpr) ([]MLOGStatement, string, error) { if _, ok := selectorExpr.X.(*ast.Ident); !ok { - return nil, "", errors.New(fmt.Sprintf("unsupported selector type: %T", selectorExpr.X)) + return nil, "", Err(ctx, fmt.Sprintf("unsupported selector type: %T", selectorExpr.X)) } name := selectorExpr.X.(*ast.Ident).Name + "." + selectorExpr.Sel.Name @@ -217,23 +211,24 @@ func selectorExprToMLOG(ident Resolvable, selectorExpr *ast.SelectorExpr) ([]MLO } } - return nil, "", errors.New(fmt.Sprintf("unknown selector: %s", name)) + return nil, "", Err(ctx, fmt.Sprintf("unknown selector: %s", name)) } -func callExprToMLOG(callExpr *ast.CallExpr, ident []Resolvable, options Options) ([]MLOGStatement, error) { +func callExprToMLOG(ctx context.Context, callExpr *ast.CallExpr, ident []Resolvable) ([]MLOGStatement, error) { results := make([]MLOGStatement, 0) var funcName string + // TODO Convert to switch if identity, ok := callExpr.Fun.(*ast.Ident); ok { funcName = identity.Name } else if selector, ok := callExpr.Fun.(*ast.SelectorExpr); ok { funcName = selector.X.(*ast.Ident).Name + "." + selector.Sel.Name } else { - return nil, errors.New(fmt.Sprintf("unknown call expression: %T", callExpr.Fun)) + return nil, Err(ctx, fmt.Sprintf("unknown call expression: %T", callExpr.Fun)) } if translatedFunc, ok := funcTranslations[funcName]; ok { - args, instructions, err := argumentsToResolvables(callExpr.Args, options) + args, instructions, err := argumentsToResolvables(ctx, callExpr.Args) if err != nil { return nil, err } @@ -250,6 +245,7 @@ func callExprToMLOG(callExpr *ast.CallExpr, ident []Resolvable, options Options) }) var value Resolvable + // TODO Convert to switch if basicLit, ok := arg.(*ast.BasicLit); ok { value = &Value{Value: basicLit.Value} } else if ident, ok := arg.(*ast.Ident); ok { @@ -257,7 +253,7 @@ func callExprToMLOG(callExpr *ast.CallExpr, ident []Resolvable, options Options) } else if argExpr, ok := arg.(ast.Expr); ok { dVar := &DynamicVariable{} - instructions, err := expressionToMLOG([]Resolvable{dVar}, argExpr, options) + instructions, err := expressionToMLOG(ctx, []Resolvable{dVar}, argExpr) if err != nil { return nil, err } @@ -265,7 +261,7 @@ func callExprToMLOG(callExpr *ast.CallExpr, ident []Resolvable, options Options) results = append(results, instructions...) value = dVar } else { - return nil, errors.New(fmt.Sprintf("unknown argument type: %T", arg)) + return nil, Err(ctx, fmt.Sprintf("unknown argument type: %T", arg)) } results = append(results, &MLOG{ @@ -286,7 +282,7 @@ func callExprToMLOG(callExpr *ast.CallExpr, ident []Resolvable, options Options) }) extra := 2 - if options.Debug { + if ctx.Value(contextOptions).(Options).Debug { extra += debugCount } @@ -329,11 +325,12 @@ func callExprToMLOG(callExpr *ast.CallExpr, ident []Resolvable, options Options) return results, nil } -func argumentsToResolvables(args []ast.Expr, options Options) ([]Resolvable, []MLOGStatement, error) { +func argumentsToResolvables(ctx context.Context, args []ast.Expr) ([]Resolvable, []MLOGStatement, error) { result := make([]Resolvable, len(args)) instructions := make([]MLOGStatement, 0) for i, arg := range args { + // TODO Convert to switch if basicExpr, ok := arg.(*ast.BasicLit); ok { result[i] = &Value{Value: basicExpr.Value} } else if identExpr, ok := arg.(*ast.Ident); ok { @@ -343,7 +340,7 @@ func argumentsToResolvables(args []ast.Expr, options Options) ([]Resolvable, []M result[i] = &NormalVariable{Name: identExpr.Name} } } else if selectorExpr, ok := arg.(*ast.SelectorExpr); ok { - _, str, err := selectorExprToMLOG(nil, selectorExpr) + _, str, err := selectorExprToMLOG(ctx, nil, selectorExpr) if err != nil { return nil, nil, err } @@ -351,7 +348,7 @@ func argumentsToResolvables(args []ast.Expr, options Options) ([]Resolvable, []M } else if expr, ok := arg.(ast.Expr); ok { dVar := &DynamicVariable{} - exprInstructions, err := expressionToMLOG([]Resolvable{dVar}, expr, options) + exprInstructions, err := expressionToMLOG(ctx, []Resolvable{dVar}, expr) if err != nil { return nil, nil, err } @@ -360,7 +357,7 @@ func argumentsToResolvables(args []ast.Expr, options Options) ([]Resolvable, []M result[i] = dVar } else { - return nil, nil, errors.New(fmt.Sprintf("unknown argument type received: %T", arg)) + return nil, nil, Err(ctx, fmt.Sprintf("unknown argument type received: %T", arg)) } } diff --git a/transpiler/main.go b/transpiler/main.go index 8e89182..00046ba 100644 --- a/transpiler/main.go +++ b/transpiler/main.go @@ -1,7 +1,7 @@ package transpiler import ( - "errors" + "context" "fmt" "go/ast" "go/parser" @@ -38,6 +38,8 @@ func GolangToMLOGBytes(input []byte, options Options) (string, error) { } func GolangToMLOG(input string, options Options) (string, error) { + ctx := context.WithValue(context.Background(), contextOptions, options) + fileSet := token.NewFileSet() f, err := parser.ParseFile(fileSet, "foo", input, 0) @@ -46,40 +48,38 @@ func GolangToMLOG(input string, options Options) (string, error) { } if f.Name.Name != "main" { - return "", errors.New("package must be main") + return "", Err(ctx, "package must be main") } for _, imp := range f.Imports { if _, ok := validImports[imp.Path.Value]; !ok { - return "", errors.New("unregistered import used: " + imp.Path.Value) + return "", Err(context.WithValue(ctx, contextSpec, imp), "unregistered import used: "+imp.Path.Value) } } constants := make([]*ast.GenDecl, 0) var mainFunc *ast.FuncDecl for _, decl := range f.Decls { - switch decl.(type) { + switch castDecl := decl.(type) { case *ast.FuncDecl: - funcDecl := decl.(*ast.FuncDecl) - if funcDecl.Name.Name == "main" { - mainFunc = funcDecl + if castDecl.Name.Name == "main" { + mainFunc = castDecl } break case *ast.GenDecl: - genDecl := decl.(*ast.GenDecl) - if genDecl.Tok.String() == "var" { - return "", errors.New("global scope may only contain constants not variables") - } else if genDecl.Tok.String() == "const" { - constants = append(constants, genDecl) + if castDecl.Tok.String() == "var" { + return "", Err(context.WithValue(ctx, contextDecl, decl), "global scope may only contain constants not variables") + } else if castDecl.Tok.String() == "const" { + constants = append(constants, castDecl) } break case *ast.BadDecl: - return "", errors.New("syntax error in input file") + return "", Err(ctx, "syntax error in input file") } } if mainFunc == nil { - return "", errors.New("file does not contain a main function") + return "", Err(ctx, "file does not contain a main function") } global := &Global{ @@ -87,27 +87,27 @@ func GolangToMLOG(input string, options Options) (string, error) { } for _, decl := range f.Decls { - switch decl.(type) { + switch castDecl := decl.(type) { case *ast.FuncDecl: - funcDecl := decl.(*ast.FuncDecl) - if funcDecl.Name.Name == "main" { + if castDecl.Name.Name == "main" { continue } - statements, err := statementToMLOG(funcDecl.Body, options) + fnCtx := context.WithValue(ctx, contextFunction, castDecl) + statements, err := statementToMLOG(fnCtx, castDecl.Body) if err != nil { return "", err } - for i, param := range funcDecl.Type.Params.List { + for i, param := range castDecl.Type.Params.List { if paramTypeIdent, ok := param.Type.(*ast.Ident); ok { if paramTypeIdent.Name != "int" && paramTypeIdent.Name != "float64" { - return "", errors.New("function parameters may only be integers or floating point numbers") + return "", Err(fnCtx, "function parameters may only be integers or floating point numbers") } } else { - return "", errors.New("function parameters may only be integers or floating point numbers") + return "", Err(fnCtx, "function parameters may only be integers or floating point numbers") } - position := len(funcDecl.Type.Params.List) - i + position := len(castDecl.Type.Params.List) - i dVar := &DynamicVariable{} @@ -147,20 +147,22 @@ func GolangToMLOG(input string, options Options) (string, error) { } global.Functions = append(global.Functions, &Function{ - Name: funcDecl.Name.Name, + Name: castDecl.Name.Name, + Declaration: castDecl, Statements: statements, - ArgumentCount: len(funcDecl.Type.Params.List), + ArgumentCount: len(castDecl.Type.Params.List), }) break } } - mainStatements, err := statementToMLOG(mainFunc.Body, options) + mainStatements, err := statementToMLOG(context.WithValue(ctx, contextFunction, mainFunc), mainFunc.Body) if err != nil { return "", err } global.Functions = append(global.Functions, &Function{ Name: mainFunc.Name.Name, + Declaration: mainFunc, Statements: mainStatements, ArgumentCount: len(mainFunc.Type.Params.List), }) @@ -186,12 +188,13 @@ func GolangToMLOG(input string, options Options) (string, error) { valueSpec := spec.(*ast.ValueSpec) for i, name := range valueSpec.Names { var value string + // TODO Convert to switch if basicLit, ok := valueSpec.Values[i].(*ast.BasicLit); ok { value = basicLit.Value } else if ident, ok := valueSpec.Values[i].(*ast.Ident); ok { value = ident.Name } else { - return "", errors.New(fmt.Sprintf("unknown constant type: %T", valueSpec.Values[i])) + return "", Err(context.WithValue(ctx, contextSpec, spec), fmt.Sprintf("unknown constant type: %T", valueSpec.Values[i])) } startup = append(startup, &MLOG{ @@ -270,14 +273,14 @@ func GolangToMLOG(input string, options Options) (string, error) { } for _, statement := range startup { - if err := statement.PostProcess(global, nil); err != nil { + if err := statement.PostProcess(context.WithValue(ctx, contextFunction, mainFunc), global, nil); err != nil { return "", err } } for _, fn := range global.Functions { for _, statement := range fn.Statements { - if err := statement.PostProcess(global, fn); err != nil { + if err := statement.PostProcess(context.WithValue(ctx, contextFunction, fn.Declaration), global, fn); err != nil { return "", err } } @@ -287,7 +290,7 @@ func GolangToMLOG(input string, options Options) (string, error) { lineNumber := 0 for _, statement := range startup { statements := statement.ToMLOG() - result += MLOGToString(statements, statement, lineNumber, options) + result += MLOGToString(context.WithValue(ctx, contextFunction, mainFunc), statements, statement, lineNumber) lineNumber += len(statements) } @@ -304,13 +307,13 @@ func GolangToMLOG(input string, options Options) (string, error) { if options.Debug { for _, debugStatement := range debugWriter { deb := debugStatement.ToMLOG() - result += MLOGToString(deb, debugStatement, lineNumber, options) + result += MLOGToString(context.WithValue(ctx, contextFunction, fn.Declaration), deb, debugStatement, lineNumber) lineNumber += len(deb) } } statements := statement.ToMLOG() - result += MLOGToString(statements, statement, lineNumber, options) + result += MLOGToString(context.WithValue(ctx, contextFunction, fn.Declaration), statements, statement, lineNumber) lineNumber += len(statements) } } diff --git a/transpiler/statement.go b/transpiler/statement.go index dfb91f8..a60e56c 100644 --- a/transpiler/statement.go +++ b/transpiler/statement.go @@ -1,51 +1,54 @@ package transpiler import ( - "errors" + "context" "fmt" "go/ast" "go/token" ) -func statementToMLOG(statement ast.Stmt, options Options) ([]MLOGStatement, error) { +func statementToMLOG(ctx context.Context, statement ast.Stmt) ([]MLOGStatement, error) { + subCtx := context.WithValue(ctx, contextStatement, statement) + results := make([]MLOGStatement, 0) - switch statement.(type) { + switch castStmt := statement.(type) { case *ast.ForStmt: - forStatement := statement.(*ast.ForStmt) - // TODO Switch from do while to while do - if len(forStatement.Body.List) == 0 { + if len(castStmt.Body.List) == 0 { break } - initMlog, err := statementToMLOG(forStatement.Init, options) + initMlog, err := statementToMLOG(subCtx, castStmt.Init) if err != nil { return nil, err } results = append(results, initMlog...) var loopStartJump *MLOGJump - if binaryExpr, ok := forStatement.Cond.(*ast.BinaryExpr); ok { + var loopEndJump *MLOGJump + if binaryExpr, ok := castStmt.Cond.(*ast.BinaryExpr); ok { if translatedOp, ok := jumpOperators[binaryExpr.Op]; ok { var leftSide Resolvable var rightSide Resolvable + // TODO Convert to switch if basicLit, ok := binaryExpr.X.(*ast.BasicLit); ok { leftSide = &Value{Value: basicLit.Value} } else if ident, ok := binaryExpr.X.(*ast.Ident); ok { leftSide = &NormalVariable{Name: ident.Name} } else { - return nil, errors.New(fmt.Sprintf("unknown left side expression type: %T", binaryExpr.X)) + return nil, Err(subCtx, fmt.Sprintf("unknown left side expression type: %T", binaryExpr.X)) } + // TODO Convert to switch if basicLit, ok := binaryExpr.Y.(*ast.BasicLit); ok { rightSide = &Value{Value: basicLit.Value} } else if ident, ok := binaryExpr.Y.(*ast.Ident); ok { rightSide = &NormalVariable{Name: ident.Name} } else { - return nil, errors.New(fmt.Sprintf("unknown right side expression type: %T", binaryExpr.Y)) + return nil, Err(subCtx, fmt.Sprintf("unknown right side expression type: %T", binaryExpr.Y)) } loopStartJump = &MLOGJump{ @@ -58,35 +61,53 @@ func statementToMLOG(statement ast.Stmt, options Options) ([]MLOGStatement, erro rightSide, }, } - results = append(results) + + loopEndJump = &MLOGJump{ + MLOG: MLOG{ + Comment: "Jump to end of loop", + }, + Condition: []Resolvable{ + &Value{Value: translatedOp}, + leftSide, + rightSide, + }, + JumpTarget: &StatementJumpTarget{ + Statement: loopStartJump, + After: true, + }, + } } else { - return nil, errors.New(fmt.Sprintf("jump statement cannot use this operation: %T", binaryExpr.Op)) + return nil, Err(subCtx, fmt.Sprintf("jump statement cannot use this operation: %T", binaryExpr.Op)) } } else { - return nil, errors.New("for loop can only have binary conditional expressions") + return nil, Err(subCtx, "for loop can only have binary conditional expressions") } - bodyMLOG, err := statementToMLOG(forStatement.Body, options) + blockCtxStruct := &ContextBlock{} + bodyMLOG, err := statementToMLOG(context.WithValue(subCtx, contextBreakableBlock, blockCtxStruct), castStmt.Body) if err != nil { return nil, err } + blockCtxStruct.Statements = bodyMLOG + + results = append(results, loopEndJump) results = append(results, bodyMLOG...) - instructions, err := statementToMLOG(forStatement.Post, options) + instructions, err := statementToMLOG(subCtx, castStmt.Post) if err != nil { return nil, err } results = append(results, instructions...) + blockCtxStruct.Extra = append(blockCtxStruct.Extra, instructions...) loopStartJump.JumpTarget = bodyMLOG[0] results = append(results, loopStartJump) + blockCtxStruct.Extra = append(blockCtxStruct.Extra, loopStartJump) break case *ast.ExprStmt: - expressionStatement := statement.(*ast.ExprStmt) - - instructions, err := expressionToMLOG(nil, expressionStatement.X, options) + instructions, err := expressionToMLOG(subCtx, nil, castStmt.X) if err != nil { return nil, err } @@ -94,10 +115,8 @@ func statementToMLOG(statement ast.Stmt, options Options) ([]MLOGStatement, erro results = append(results, instructions...) break case *ast.IfStmt: - ifStmt := statement.(*ast.IfStmt) - - if ifStmt.Init != nil { - instructions, err := statementToMLOG(ifStmt.Init, options) + if castStmt.Init != nil { + instructions, err := statementToMLOG(subCtx, castStmt.Init) if err != nil { return nil, err } @@ -105,12 +124,12 @@ func statementToMLOG(statement ast.Stmt, options Options) ([]MLOGStatement, erro } var condVar Resolvable - if condIdent, ok := ifStmt.Cond.(*ast.Ident); ok { + if condIdent, ok := castStmt.Cond.(*ast.Ident); ok { condVar = &NormalVariable{Name: condIdent.Name} } else { condVar = &DynamicVariable{} - instructions, err := expressionToMLOG([]Resolvable{condVar}, ifStmt.Cond, options) + instructions, err := expressionToMLOG(subCtx, []Resolvable{condVar}, castStmt.Cond) if err != nil { return nil, err } @@ -118,7 +137,7 @@ func statementToMLOG(statement ast.Stmt, options Options) ([]MLOGStatement, erro results = append(results, instructions...) } - blockInstructions, err := statementToMLOG(ifStmt.Body, options) + blockInstructions, err := statementToMLOG(subCtx, castStmt.Body) if err != nil { return nil, err } @@ -153,8 +172,8 @@ func statementToMLOG(statement ast.Stmt, options Options) ([]MLOGStatement, erro results = append(results, blockInstructions...) - if ifStmt.Else != nil { - elseInstructions, err := statementToMLOG(ifStmt.Else, options) + if castStmt.Else != nil { + elseInstructions, err := statementToMLOG(subCtx, castStmt.Else) if err != nil { return nil, err } @@ -179,24 +198,23 @@ func statementToMLOG(statement ast.Stmt, options Options) ([]MLOGStatement, erro break case *ast.AssignStmt: - assignMlog, err := assignStmtToMLOG(statement.(*ast.AssignStmt), options) + assignMlog, err := assignStmtToMLOG(subCtx, castStmt) if err != nil { return nil, err } results = append(results, assignMlog...) break case *ast.ReturnStmt: - returnStmt := statement.(*ast.ReturnStmt) - - if len(returnStmt.Results) > 1 { + if len(castStmt.Results) > 1 { // TODO Multi-value returns - return nil, errors.New("only single value returns are supported") + return nil, Err(subCtx, "only single value returns are supported") } - if len(returnStmt.Results) > 0 { - returnValue := returnStmt.Results[0] + if len(castStmt.Results) > 0 { + returnValue := castStmt.Results[0] var resultVar Resolvable + // TODO Convert to switch if ident, ok := returnValue.(*ast.Ident); ok { resultVar = &NormalVariable{Name: ident.Name} } else if basicLit, ok := returnValue.(*ast.BasicLit); ok { @@ -204,7 +222,7 @@ func statementToMLOG(statement ast.Stmt, options Options) ([]MLOGStatement, erro } else if expr, ok := returnValue.(ast.Expr); ok { dVar := &DynamicVariable{} - instructions, err := expressionToMLOG([]Resolvable{dVar}, expr, options) + instructions, err := expressionToMLOG(subCtx, []Resolvable{dVar}, expr) if err != nil { return nil, err } @@ -212,7 +230,7 @@ func statementToMLOG(statement ast.Stmt, options Options) ([]MLOGStatement, erro results = append(results, instructions...) resultVar = dVar } else { - return nil, errors.New(fmt.Sprintf("unknown return value type: %T", returnValue)) + return nil, Err(subCtx, fmt.Sprintf("unknown return value type: %T", returnValue)) } results = append(results, &MLOG{ @@ -230,20 +248,22 @@ func statementToMLOG(statement ast.Stmt, options Options) ([]MLOGStatement, erro results = append(results, &MLOGTrampolineBack{}) break case *ast.BlockStmt: - blockStmt := statement.(*ast.BlockStmt) - for _, s := range blockStmt.List { - instructions, err := statementToMLOG(s, options) + blockCtxStruct := &ContextBlock{} + statements := make([]MLOGStatement, 0) + for _, s := range castStmt.List { + instructions, err := statementToMLOG(context.WithValue(subCtx, contextBlock, blockCtxStruct), s) if err != nil { return nil, err } - results = append(results, instructions...) + statements = append(statements, instructions...) } + blockCtxStruct.Statements = statements + results = append(results, statements...) break case *ast.IncDecStmt: - incDecStatement := statement.(*ast.IncDecStmt) - name := &NormalVariable{Name: incDecStatement.X.(*ast.Ident).Name} + name := &NormalVariable{Name: castStmt.X.(*ast.Ident).Name} op := "add" - if incDecStatement.Tok == token.DEC { + if castStmt.Tok == token.DEC { op = "sub" } results = append(results, &MLOG{ @@ -258,15 +278,165 @@ func statementToMLOG(statement ast.Stmt, options Options) ([]MLOGStatement, erro }, }, }) + break + case *ast.BranchStmt: + switch castStmt.Tok { + case token.BREAK: + block := ctx.Value(contextBreakableBlock) + if block == nil { + return nil, Err(subCtx, fmt.Sprintf("branch statement outside any breakable block scope")) + } + results = append(results, &MLOGBreak{ + Block: block.(*ContextBlock), + }) + break + case token.CONTINUE: + block := ctx.Value(contextBreakableBlock) + if block == nil { + return nil, Err(subCtx, fmt.Sprintf("branch statement outside any breakable block scope")) + } + results = append(results, &MLOGContinue{ + Block: block.(*ContextBlock), + }) + break + case token.FALLTHROUGH: + block := ctx.Value(contextSwitchClauseBlock) + if block == nil { + return nil, Err(subCtx, fmt.Sprintf("fallthrough statement outside switch scope")) + } + results = append(results, &MLOGFallthrough{ + Block: block.(*ContextBlock), + }) + break + default: + return nil, Err(subCtx, fmt.Sprintf("branch statement not supported: %s", castStmt.Tok)) + } + break + case *ast.SwitchStmt: + if castStmt.Init != nil { + instructions, err := statementToMLOG(subCtx, castStmt.Init) + if err != nil { + return nil, err + } + results = append(results, instructions...) + } + + // TODO Convert to switch + var tag Resolvable + if tagBasic, ok := castStmt.Tag.(*ast.BasicLit); ok { + tag = &Value{Value: tagBasic.Value} + } else if tagIdent, ok := castStmt.Tag.(*ast.Ident); ok { + tag = &NormalVariable{Name: tagIdent.Name} + } else { + return nil, Err(subCtx, fmt.Sprintf("unknown switch condition type: %T", castStmt.Tag)) + } + + blockCtxStruct := &ContextBlock{} + blockCtx := context.WithValue(subCtx, contextBreakableBlock, blockCtxStruct) + instructions := make([]MLOGStatement, 0) + + var previousSwitchClause *ContextBlock + for _, switchStmt := range castStmt.Body.List { + if caseStmt, ok := switchStmt.(*ast.CaseClause); ok { + statements := make([]MLOGStatement, 0) + switchClauseBlockCtxStruct := &ContextBlock{} + for _, s := range caseStmt.Body { + bodyInstructions, err := statementToMLOG(context.WithValue(blockCtx, contextSwitchClauseBlock, switchClauseBlockCtxStruct), s) + if err != nil { + return nil, err + } + statements = append(statements, bodyInstructions...) + } + switchClauseBlockCtxStruct.Statements = statements + + for _, caseExpr := range caseStmt.List { + var caseTag Resolvable + if tagBasic, ok := caseExpr.(*ast.BasicLit); ok { + caseTag = &Value{Value: tagBasic.Value} + } else if tagIdent, ok := caseExpr.(*ast.Ident); ok { + caseTag = &NormalVariable{Name: tagIdent.Name} + } else { + return nil, Err(subCtx, fmt.Sprintf("unknown switch case condition type: %T", caseExpr)) + } + + jumpIn := &MLOGJump{ + MLOG: MLOG{ + Comment: "Jump in if match", + }, + Condition: []Resolvable{ + &Value{Value: "equal"}, + tag, + caseTag, + }, + JumpTarget: &StatementJumpTarget{ + Statement: statements[0], + }, + } + instructions = append(instructions, jumpIn) + if previousSwitchClause != nil { + previousSwitchClause.Extra = append(previousSwitchClause.Extra, jumpIn) + } + } + + var skipClause *MLOGJump + if len(caseStmt.List) > 0 { + skipClause = &MLOGJump{ + MLOG: MLOG{ + Comment: "Otherwise skip clause", + }, + Condition: []Resolvable{ + &Value{Value: "always"}, + }, + JumpTarget: &StatementJumpTarget{ + Statement: statements[len(statements)-1], + After: true, + }, + } + instructions = append(instructions, skipClause) + if previousSwitchClause != nil { + previousSwitchClause.Extra = append(previousSwitchClause.Extra, skipClause) + } + } + + instructions = append(instructions, statements...) + + addJump := len(caseStmt.Body) == 0 + if len(caseStmt.Body) >= 0 { + if _, ok := caseStmt.Body[len(caseStmt.Body)-1].(*ast.BranchStmt); !ok { + addJump = true + } + } + + if addJump { + endBreak := &MLOGBreak{ + Block: blockCtxStruct, + } + instructions = append(instructions, endBreak) + + if skipClause != nil { + skipClause.JumpTarget.(*StatementJumpTarget).Statement = endBreak + } + } + + previousSwitchClause = switchClauseBlockCtxStruct + } else { + return nil, Err(subCtx, "switch statement may only contain case and default statements") + } + } + + blockCtxStruct.Statements = instructions + + results = append(results, instructions...) + break default: - return nil, errors.New(fmt.Sprintf("statement type not supported: %T", statement)) + return nil, Err(subCtx, fmt.Sprintf("statement type not supported: %T", statement)) } return results, nil } -func assignStmtToMLOG(statement *ast.AssignStmt, options Options) ([]MLOGStatement, error) { +func assignStmtToMLOG(ctx context.Context, statement *ast.AssignStmt) ([]MLOGStatement, error) { mlog := make([]MLOGStatement, 0) if len(statement.Lhs) != len(statement.Rhs) { @@ -277,28 +447,28 @@ func assignStmtToMLOG(statement *ast.AssignStmt, options Options) ([]MLOGStateme leftSide[i] = &NormalVariable{Name: lhs.(*ast.Ident).Name} } - exprMLOG, err := expressionToMLOG(leftSide, statement.Rhs[0], options) + exprMLOG, err := expressionToMLOG(ctx, leftSide, statement.Rhs[0]) if err != nil { return nil, err } mlog = append(mlog, exprMLOG...) } else { - return nil, errors.New("mismatched variable assignment sides") + return nil, Err(ctx, "mismatched variable assignment sides") } } else { for i, expr := range statement.Lhs { if ident, ok := expr.(*ast.Ident); ok { if statement.Tok != token.ASSIGN && statement.Tok != token.DEFINE { - return nil, errors.New("only direct assignment is supported") + return nil, Err(ctx, "only direct assignment is supported") } - exprMLOG, err := expressionToMLOG([]Resolvable{&NormalVariable{Name: ident.Name}}, statement.Rhs[i], options) + exprMLOG, err := expressionToMLOG(ctx, []Resolvable{&NormalVariable{Name: ident.Name}}, statement.Rhs[i]) if err != nil { return nil, err } mlog = append(mlog, exprMLOG...) } else { - return nil, errors.New("left side variable assignment can only contain identifications") + return nil, Err(ctx, "left side variable assignment can only contain identifications") } } } diff --git a/transpiler/types.go b/transpiler/types.go index 2cd5355..0061bf7 100644 --- a/transpiler/types.go +++ b/transpiler/types.go @@ -1,8 +1,9 @@ package transpiler import ( - "errors" + "context" "fmt" + "go/ast" "strconv" ) @@ -13,6 +14,7 @@ type Global struct { type Function struct { Name string + Declaration *ast.FuncDecl Statements []MLOGStatement ArgumentCount int VariableCounter int @@ -24,7 +26,7 @@ type MLOGAble interface { } type Processable interface { - PostProcess(*Global, *Function) error + PostProcess(context.Context, *Global, *Function) error } type WithPosition interface { @@ -43,7 +45,7 @@ type MLOGStatement interface { Processable } -func MLOGToString(statements [][]Resolvable, statement MLOGAble, lineNumber int, options Options) string { +func MLOGToString(ctx context.Context, statements [][]Resolvable, statement MLOGAble, lineNumber int) string { result := "" for _, line := range statements { resultLine := "" @@ -54,11 +56,11 @@ func MLOGToString(statements [][]Resolvable, statement MLOGAble, lineNumber int, resultLine += t.GetValue() } - if options.Numbers { + if ctx.Value(contextOptions).(Options).Numbers { result += fmt.Sprintf("%3d: ", lineNumber) } - if options.Comments { + if ctx.Value(contextOptions).(Options).Comments { result += fmt.Sprintf("%-45s", resultLine) result += " // " + statement.GetComment() } else { @@ -81,10 +83,10 @@ func (m *MLOG) ToMLOG() [][]Resolvable { return m.Statement } -func (m *MLOG) PostProcess(global *Global, function *Function) error { +func (m *MLOG) PostProcess(ctx context.Context, global *Global, function *Function) error { for _, resolvables := range m.Statement { for _, resolvable := range resolvables { - if err := resolvable.PostProcess(global, function); err != nil { + if err := resolvable.PostProcess(ctx, global, function); err != nil { return err } } @@ -97,6 +99,10 @@ func (m *MLOG) GetPosition() int { } func (m *MLOG) Size() int { + if len(m.Statement) == 0 { + panic("statement without instructions") + } + return len(m.Statement) } @@ -138,13 +144,13 @@ func (m *MLOGFunc) SetPosition(position int) int { return m.Function.Count } -func (m *MLOGFunc) PostProcess(global *Global, function *Function) error { +func (m *MLOGFunc) PostProcess(ctx context.Context, global *Global, function *Function) error { if len(m.Variables) != m.Function.Variables { - return errors.New(fmt.Sprintf("function requires %d variables, provided: %d", m.Function.Variables, len(m.Variables))) + return Err(ctx, fmt.Sprintf("function requires %d variables, provided: %d", m.Function.Variables, len(m.Variables))) } for _, argument := range m.Arguments { - if err := argument.PostProcess(global, function); err != nil { + if err := argument.PostProcess(ctx, global, function); err != nil { return err } } @@ -157,7 +163,7 @@ func (m *MLOGFunc) PostProcess(global *Global, function *Function) error { for i, statement := range m.Unresolved { statement.SetPosition(m.Position + i) - if err := statement.PostProcess(global, function); err != nil { + if err := statement.PostProcess(ctx, global, function); err != nil { return err } } @@ -192,13 +198,13 @@ func (m *MLOGJump) Size() int { return 1 } -func (m *MLOGJump) PostProcess(global *Global, function *Function) error { +func (m *MLOGJump) PostProcess(ctx context.Context, global *Global, function *Function) error { for _, resolvable := range m.Condition { - if err := resolvable.PostProcess(global, function); err != nil { + if err := resolvable.PostProcess(ctx, global, function); err != nil { return err } } - return m.JumpTarget.PostProcess(global, function) + return m.JumpTarget.PostProcess(ctx, global, function) } func (m *MLOGJump) GetComment() string { @@ -221,14 +227,14 @@ func (m *FunctionJumpTarget) Size() int { return 1 } -func (m *FunctionJumpTarget) PostProcess(global *Global, _ *Function) error { +func (m *FunctionJumpTarget) PostProcess(ctx context.Context, global *Global, _ *Function) error { for _, fn := range global.Functions { if fn.Name == m.FunctionName { m.Statement = fn.Statements[0] return nil } } - return errors.New("unknown function: " + m.FunctionName) + return Err(ctx, "unknown function: "+m.FunctionName) } type Resolvable interface { @@ -244,7 +250,7 @@ func (m *Value) GetValue() string { return m.Value } -func (m *Value) PostProcess(*Global, *Function) error { +func (m *Value) PostProcess(context.Context, *Global, *Function) error { return nil } @@ -257,7 +263,7 @@ type NormalVariable struct { CalculatedName string } -func (m *NormalVariable) PostProcess(global *Global, function *Function) error { +func (m *NormalVariable) PostProcess(ctx context.Context, global *Global, function *Function) error { if m.CalculatedName == "" { if _, ok := global.Constants[m.Name]; ok { m.CalculatedName = m.Name @@ -279,7 +285,7 @@ type DynamicVariable struct { Name string } -func (m *DynamicVariable) PostProcess(global *Global, function *Function) error { +func (m *DynamicVariable) PostProcess(ctx context.Context, global *Global, function *Function) error { if m.Name == "" { suffix := function.VariableCounter function.VariableCounter += 1 @@ -354,7 +360,7 @@ func (m *StatementJumpTarget) Size() int { return 1 } -func (m *StatementJumpTarget) PostProcess(*Global, *Function) error { +func (m *StatementJumpTarget) PostProcess(context.Context, *Global, *Function) error { return nil } @@ -376,3 +382,81 @@ func (m *MLOGTrampolineBack) ToMLOG() [][]Resolvable { func (m *MLOGTrampolineBack) GetComment() string { return "Trampoline back" } + +type MLOGBreak struct { + MLOG + Block *ContextBlock +} + +func (m *MLOGBreak) ToMLOG() [][]Resolvable { + lastStatement := m.Block.Statements[len(m.Block.Statements)-1] + if m.Block.Extra != nil && len(m.Block.Extra) > 0 { + lastStatement = m.Block.Extra[len(m.Block.Extra)-1] + } + return [][]Resolvable{ + { + &Value{Value: "jump"}, + &Value{Value: strconv.Itoa(lastStatement.GetPosition() + lastStatement.Size())}, + &Value{Value: "always"}, + }, + } +} + +func (m *MLOGBreak) Size() int { + return 1 +} + +func (m *MLOGBreak) GetComment() string { + return "Break" +} + +type MLOGContinue struct { + MLOG + Block *ContextBlock +} + +func (m *MLOGContinue) ToMLOG() [][]Resolvable { + lastStatement := m.Block.Statements[len(m.Block.Statements)-1] + return [][]Resolvable{ + { + &Value{Value: "jump"}, + &Value{Value: strconv.Itoa(lastStatement.GetPosition() + lastStatement.Size())}, + &Value{Value: "always"}, + }, + } +} + +func (m *MLOGContinue) Size() int { + return 1 +} + +func (m *MLOGContinue) GetComment() string { + return "Continue" +} + +type MLOGFallthrough struct { + MLOG + Block *ContextBlock +} + +func (m *MLOGFallthrough) ToMLOG() [][]Resolvable { + lastStatement := m.Block.Statements[len(m.Block.Statements)-1] + if m.Block.Extra != nil && len(m.Block.Extra) > 0 { + lastStatement = m.Block.Extra[len(m.Block.Extra)-1] + } + return [][]Resolvable{ + { + &Value{Value: "jump"}, + &Value{Value: strconv.Itoa(lastStatement.GetPosition() + lastStatement.Size())}, + &Value{Value: "always"}, + }, + } +} + +func (m *MLOGFallthrough) Size() int { + return 1 +} + +func (m *MLOGFallthrough) GetComment() string { + return "Fallthrough" +}