diff --git a/README.md b/README.md index a2661d3..50583d6 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ Go to MLOG transpiler. A Web IDE is available [here](https://vilsol.github.io/go-mlog-web/?1) +## Examples + +There are several example programs available on the wiki. // TODO + ## Supports * Functions @@ -27,13 +31,18 @@ A Web IDE is available [here](https://vilsol.github.io/go-mlog-web/?1) * Contextual errors * Tree-shaking unused functions * Multi-pass pre/post-processing +* Stackless functions ## Roadmap * Full variable block scoping * Variable argument count functions * Multiple function return values -* Optimize simple jump instructions + +## Planned Optimizations + +* Simple jump instructions +* Switch case jumps at the start of the block ## Design Limitations @@ -46,7 +55,7 @@ A Web IDE is available [here](https://vilsol.github.io/go-mlog-web/?1) * Transpilation optimizations * MLOG Runtime -## Usage +## CLI Usage ``` Usage: diff --git a/cmd/root.go b/cmd/root.go index a63f7c1..be3bf33 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -55,6 +55,7 @@ func init() { rootCmd.PersistentFlags().Bool("numbers", false, "Output line numbers") rootCmd.PersistentFlags().Bool("comments", false, "Output comments") rootCmd.PersistentFlags().Bool("debug", false, "Write to debug memory cell") + rootCmd.PersistentFlags().String("stacked", "", "Use a provided memory cell/bank as a stack") rootCmd.PersistentFlags().String("output", "", "Output file. Outputs to stdout if unspecified") diff --git a/cmd/transpile.go b/cmd/transpile.go index c2f5ee3..8956ff0 100644 --- a/cmd/transpile.go +++ b/cmd/transpile.go @@ -20,7 +20,7 @@ var transpileCmd = &cobra.Command{ result, err := transpiler.GolangToMLOGFile(args[0], transpiler.Options{ Numbers: viper.GetBool("numbers"), Comments: viper.GetBool("comments"), - Debug: viper.GetBool("debug"), + Stacked: viper.GetString("stacked"), }) if err != nil { return err diff --git a/m/base.go b/m/base.go index e0bf87e..dd34a0f 100644 --- a/m/base.go +++ b/m/base.go @@ -15,8 +15,9 @@ func init() { Translate: func(args []transpiler.Resolvable, vars []transpiler.Resolvable) ([]transpiler.MLOGStatement, error) { memoryName := strings.Trim(args[0].GetValue(), "\"") - if memoryName == transpiler.StackCellName { - return nil, errors.New("can't read/write to memory cell that is used for the stack: " + transpiler.StackCellName) + // TODO Remove hardcode + if memoryName == "bank1" { + return nil, errors.New("can't read/write to memory cell that is used for the stack: bank1") } return []transpiler.MLOGStatement{ @@ -40,8 +41,8 @@ func init() { Translate: func(args []transpiler.Resolvable, _ []transpiler.Resolvable) ([]transpiler.MLOGStatement, error) { memoryName := strings.Trim(args[1].GetValue(), "\"") - if memoryName == transpiler.StackCellName { - return nil, errors.New("can't read/write to memory cell that is used for the stack: " + transpiler.StackCellName) + if memoryName == "bank1" { + return nil, errors.New("can't read/write to memory cell that is used for the stack: bank1") } return []transpiler.MLOGStatement{ @@ -132,8 +133,8 @@ func init() { { &transpiler.Value{Value: "sensor"}, vars[0], - &transpiler.Value{Value: args[0].GetValue()}, - &transpiler.Value{Value: args[1].GetValue()}, + &transpiler.Value{Value: strings.Trim(args[0].GetValue(), "\"")}, + &transpiler.Value{Value: strings.Trim(args[1].GetValue(), "\"")}, }, }, }, @@ -176,6 +177,6 @@ func Radar(from string, target1 RadarTarget, target2 RadarTarget, target3 RadarT } // Extract information indicated by sense from the provided block -func Sensor(block string, sense string) int { +func Sensor(block interface{}, sense string) float64 { return 0 } diff --git a/m/draw.go b/m/draw.go index 180cf0e..904e7ff 100644 --- a/m/draw.go +++ b/m/draw.go @@ -208,7 +208,7 @@ func init() { &transpiler.Value{Value: "image"}, &transpiler.Value{Value: args[0].GetValue()}, &transpiler.Value{Value: args[1].GetValue()}, - &transpiler.Value{Value: args[2].GetValue()}, + &transpiler.Value{Value: strings.Trim(args[2].GetValue(), "\"")}, &transpiler.Value{Value: args[3].GetValue()}, &transpiler.Value{Value: args[4].GetValue()}, }, @@ -263,11 +263,11 @@ func DrawLineRect(x int, y int, width int, height int) { } // Draw a filled equilateral polygon centered around the provided point -func DrawPoly(x int, y int, sides int, radius float32, rotation float32) { +func DrawPoly(x int, y int, sides int, radius float64, rotation float64) { } // Draw an outlined equilateral polygon centered around the provided point -func DrawLinePoly(x int, y int, sides int, radius float32, rotation float32) { +func DrawLinePoly(x int, y int, sides int, radius float64, rotation float64) { } // Draw a filled triangle between provided points @@ -275,7 +275,7 @@ func DrawTriangle(x1 int, y1 int, x2 int, y2 int, x3 int, y3 int) { } // Draw provided icon centered around the provided point -func DrawImage(x int, y int, image string, size float32, rotation float32) { +func DrawImage(x int, y int, image string, size float64, rotation float64) { } // Flush all draw statements to the provided display block diff --git a/m/operations.go b/m/operations.go index b9213df..e9ff5fa 100644 --- a/m/operations.go +++ b/m/operations.go @@ -43,6 +43,47 @@ func init() { }, nil }, }) + transpiler.RegisterFuncTranslation("m.Log10", transpiler.Translator{ + Count: func(args []transpiler.Resolvable, vars []transpiler.Resolvable) int { + return 1 + }, + Variables: 1, + Translate: func(args []transpiler.Resolvable, vars []transpiler.Resolvable) ([]transpiler.MLOGStatement, error) { + return []transpiler.MLOGStatement{ + &transpiler.MLOG{ + Statement: [][]transpiler.Resolvable{ + { + &transpiler.Value{Value: "op"}, + &transpiler.Value{Value: "log10"}, + vars[0], + &transpiler.Value{Value: args[0].GetValue()}, + }, + }, + }, + }, nil + }, + }) + transpiler.RegisterFuncTranslation("m.Ceil", transpiler.Translator{ + Count: func(args []transpiler.Resolvable, vars []transpiler.Resolvable) int { + return 1 + }, + Variables: 1, + Translate: func(args []transpiler.Resolvable, vars []transpiler.Resolvable) ([]transpiler.MLOGStatement, error) { + return []transpiler.MLOGStatement{ + &transpiler.MLOG{ + Statement: [][]transpiler.Resolvable{ + { + &transpiler.Value{Value: "op"}, + &transpiler.Value{Value: "ceil"}, + vars[0], + &transpiler.Value{Value: args[0].GetValue()}, + }, + }, + }, + }, nil + }, + }) + //op idiv result a b } // TODO Operations @@ -52,7 +93,22 @@ func Floor(number float64) int { return 0 } +// Ceil the provided floating point number and convert to integer +func Ceil(number float64) int { + return 0 +} + +// Perform an integer division on provided numbers +func IntDiv(a int, b int) int { + return 0 +} + // Generate a random floating point number between 0 (inclusive) and max (exclusive) func Random(max float64) float64 { return 0 } + +// Return a log10 function of the input number +func Log10(number float64) float64 { + return 0 +} diff --git a/m/types.go b/m/types.go index 70bd056..3ad9b31 100644 --- a/m/types.go +++ b/m/types.go @@ -62,7 +62,9 @@ type Unit = interface{} type HealthC = interface{} -type Building = interface{} +type Building = struct { + HealthC +} type BlockFlag = string diff --git a/m/unit.go b/m/unit.go index 0d075e8..6f858ef 100644 --- a/m/unit.go +++ b/m/unit.go @@ -16,7 +16,7 @@ func init() { Statement: [][]transpiler.Resolvable{ { &transpiler.Value{Value: "ubind"}, - &transpiler.Value{Value: args[0].GetValue()}, + &transpiler.Value{Value: strings.Trim(args[0].GetValue(), "\"")}, }, }, }, @@ -177,19 +177,19 @@ func UnitLocateOre(ore string) (x int, y int, found bool) { // // Also locates blocks outside the range of the unit func UnitLocateBuilding(buildingType BlockFlag, enemy bool) (x int, y int, found bool, building Building) { - return 0, 0, false, nil + return 0, 0, false, Building{} } // Locate the enemy spawn // // Also locates blocks outside the range of the unit func UnitLocateSpawn() (x int, y int, found bool, building Building) { - return 0, 0, false, nil + return 0, 0, false, Building{} } // Locate a damaged building // // Also locates blocks outside the range of the unit func UnitLocateDamaged() (x int, y int, found bool, building Building) { - return 0, 0, false, nil + return 0, 0, false, Building{} } diff --git a/m/unit_control.go b/m/unit_control.go index 3abcf41..04dc2c7 100644 --- a/m/unit_control.go +++ b/m/unit_control.go @@ -351,7 +351,7 @@ func UnitTarget(x float64, y float64, shoot bool) { // Shoot with the cached unit at the predicted position of target unit // // If shoot parameter is false, it will cease firing -func UnitTargetP(target int, shoot bool) { +func UnitTargetP(target HealthC, shoot bool) { } // Drops items into the provided building @@ -396,7 +396,7 @@ func UnitBuild(x float64, y float64, block string, rotation int, config int) { // Retrieve the building and its type at the specified absolute position func UnitGetBlock(x float64, y float64) (blockType string, building Building) { - return "", nil + return "", Building{} } // Checks whether there is a unit within the specified radius around the provided absolute position diff --git a/tests/base_test.go b/tests/base_test.go index 8ad8be0..f59245a 100644 --- a/tests/base_test.go +++ b/tests/base_test.go @@ -41,7 +41,7 @@ func TestBase(t *testing.T) { { name: "Sensor", input: TestMain(`x := m.Sensor("A", "B")`), - output: `sensor _main_x "A" "B"`, + output: `sensor _main_x A B`, }, } for _, test := range tests { diff --git a/tests/constant_test.go b/tests/constant_test.go index bd4596f..f288385 100644 --- a/tests/constant_test.go +++ b/tests/constant_test.go @@ -23,10 +23,9 @@ const y = x func main() { print(x) }`, - output: `set @stack 0 -set x 1 + output: `set x 1 set y x -jump 4 always +jump 3 always print x`, }, } diff --git a/tests/draw_test.go b/tests/draw_test.go index 2172ab5..a0e72bc 100644 --- a/tests/draw_test.go +++ b/tests/draw_test.go @@ -61,7 +61,7 @@ func TestDraw(t *testing.T) { { name: "DrawImage", input: TestMain(`m.DrawImage(1, 2, "A", 4, 5)`), - output: `draw image 1 2 "A" 4 5`, + output: `draw image 1 2 A 4 5`, }, { name: "DrawFlush", diff --git a/tests/options_test.go b/tests/options_test.go index 31d97b9..91e1fe2 100644 --- a/tests/options_test.go +++ b/tests/options_test.go @@ -32,87 +32,22 @@ func foo(x int) int { options: transpiler.Options{ Numbers: true, }, - output: ` 0: set @stack 0 - 1: jump 7 always - 2: op sub _foo_0 @stack 1 - 3: read _foo_x bank1 _foo_0 - 4: op add _foo_1 _foo_x 20 - 5: set @return _foo_1 - 6: read @counter bank1 @stack - 7: set _main_i 0 - 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", - input: testInput, - options: transpiler.Options{ - Debug: true, - }, - output: `set @stack 0 -jump 19 always -write @counter cell2 0 -write @stack cell2 1 -op sub _foo_0 @stack 1 -write @counter cell2 0 -write @stack cell2 1 -read _foo_x bank1 _foo_0 -write @counter cell2 0 -write @stack cell2 1 -op add _foo_1 _foo_x 20 -write @counter cell2 0 -write @stack cell2 1 -set @return _foo_1 -write @counter cell2 0 -write @stack cell2 1 -read @counter bank1 @stack -write @counter cell2 0 -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 -write _main_i bank1 @stack -write @counter cell2 0 -write @stack cell2 1 -op add @stack @stack 1 -write @counter cell2 0 -write @stack cell2 1 -write 38 bank1 @stack -write @counter cell2 0 -write @stack cell2 1 -jump 4 always -write @counter cell2 0 -write @stack cell2 1 -op sub @stack @stack 2 -write @counter cell2 0 -write @stack cell2 1 -set _main_0 @return -write @counter cell2 0 -write @stack cell2 1 -print _main_0 -print "\n" -write @counter cell2 0 -write @stack cell2 1 -op add _main_i _main_i 1 -write @counter cell2 0 -write @stack cell2 1 -jump 25 lessThan _main_i 10`, + output: ` 0: jump 5 always + 1: set _foo_x @funcArg_foo_0 + 2: op add _foo_0 _foo_x 20 + 3: set @return _foo_0 + 4: set @counter @funcTramp_foo + 5: set _main_i 0 + 6: jump 8 lessThan _main_i 10 + 7: jump 16 always + 8: set @funcArg_foo_0 _main_i + 9: set @funcTramp_foo 11 + 10: jump 1 always + 11: set _main_0 @return + 12: print _main_0 + 13: print "\n" + 14: op add _main_i _main_i 1 + 15: jump 8 lessThan _main_i 10`, }, { name: "Comments", @@ -120,30 +55,26 @@ jump 25 lessThan _main_i 10`, options: transpiler.Options{ Comments: true, }, - output: `set @stack 0 // Reset Stack -jump 7 always // Jump to start of main + output: `jump 5 always // Jump to start of main // Function: foo // -op sub _foo_0 @stack 1 // Calculate address of parameter -read _foo_x bank1 _foo_0 // Read parameter into variable -op add _foo_1 _foo_x 20 // Execute operation -set @return _foo_1 // Set return data -read @counter bank1 @stack // Trampoline back +set _foo_x @funcArg_foo_0 // Read parameter into variable +op add _foo_0 _foo_x 20 // Execute operation +set @return _foo_0 // Set return data +set @counter @funcTramp_foo // 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 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 +jump 8 lessThan _main_i 10 // Jump into the loop +jump 16 always // Jump to end of loop +set @funcArg_foo_0 _main_i // Set foo argument: 0 +set @funcTramp_foo 11 // Set Trampoline Address +jump 1 always // Jump to function: foo +set _main_0 @return // Set variable to returned value print _main_0 // Call to native function print "\n" // Call to native function op add _main_i _main_i 1 // Execute increment/decrement -jump 9 lessThan _main_i 10 // Jump to start of loop`, +jump 8 lessThan _main_i 10 // Jump to start of loop`, }, { name: "All", @@ -152,30 +83,26 @@ jump 9 lessThan _main_i 10 // Jump to start of loop`, Numbers: true, Comments: true, }, - output: ` 0: set @stack 0 // Reset Stack - 1: jump 7 always // Jump to start of main + output: ` 0: jump 5 always // Jump to start of main // Function: foo // - 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 + 1: set _foo_x @funcArg_foo_0 // Read parameter into variable + 2: op add _foo_0 _foo_x 20 // Execute operation + 3: set @return _foo_0 // Set return data + 4: set @counter @funcTramp_foo // Trampoline back // Function: main // - 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 increment/decrement - 19: jump 9 lessThan _main_i 10 // Jump to start of loop`, + 5: set _main_i 0 // Set the variable to the value + 6: jump 8 lessThan _main_i 10 // Jump into the loop + 7: jump 16 always // Jump to end of loop + 8: set @funcArg_foo_0 _main_i // Set foo argument: 0 + 9: set @funcTramp_foo 11 // Set Trampoline Address + 10: jump 1 always // Jump to function: foo + 11: set _main_0 @return // Set variable to returned value + 12: print _main_0 // Call to native function + 13: print "\n" // Call to native function + 14: op add _main_i _main_i 1 // Execute increment/decrement + 15: jump 8 lessThan _main_i 10 // Jump to start of loop`, }, } for _, test := range tests { diff --git a/tests/function_test.go b/tests/stacked_function_test.go similarity index 97% rename from tests/function_test.go rename to tests/stacked_function_test.go index 1f502d2..49f9bce 100644 --- a/tests/function_test.go +++ b/tests/stacked_function_test.go @@ -7,7 +7,7 @@ import ( "testing" ) -func TestFunction(t *testing.T) { +func TestStackedFunction(t *testing.T) { tests := []struct { name string input string @@ -38,12 +38,12 @@ read _sampleDynamic_arg1 bank1 _sampleDynamic_1 op add _sampleDynamic_2 _sampleDynamic_arg1 _sampleDynamic_arg2 set @return _sampleDynamic_2 read @counter bank1 @stack -op add @stack @stack 1 op add _main_0 1 2 -write _main_0 bank1 @stack op add @stack @stack 1 +write _main_0 bank1 @stack op rand _main_1 100 op floor _main_2 _main_1 +op add @stack @stack 1 write _main_2 bank1 @stack op add @stack @stack 1 write 19 bank1 @stack @@ -166,7 +166,9 @@ op sub @stack @stack 1`, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{}) + mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{ + Stacked: "bank1", + }) if err != nil { t.Error(err) diff --git a/tests/stackless_function_test.go b/tests/stackless_function_test.go new file mode 100644 index 0000000..0c42c62 --- /dev/null +++ b/tests/stackless_function_test.go @@ -0,0 +1,160 @@ +package tests + +import ( + "github.com/Vilsol/go-mlog/transpiler" + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestStacklessFunction(t *testing.T) { + tests := []struct { + name string + input string + output string + }{ + { + name: "FunctionDynamicReturn", + input: `package main + +import ( + "github.com/Vilsol/go-mlog/m" + "github.com/Vilsol/go-mlog/x" +) + +func main() { + print(sampleDynamic(1 + 2, m.Floor(m.Random(100)))) +} + +func sampleDynamic(arg1 int, arg2 int) int { + return arg1 + arg2 +}`, + output: `jump 6 always +set _sampleDynamic_arg2 @funcArg_sampleDynamic_1 +set _sampleDynamic_arg1 @funcArg_sampleDynamic_0 +op add _sampleDynamic_0 _sampleDynamic_arg1 _sampleDynamic_arg2 +set @return _sampleDynamic_0 +set @counter @funcTramp_sampleDynamic +op add _main_0 1 2 +set @funcArg_sampleDynamic_0 _main_0 +op rand _main_1 100 +op floor _main_2 _main_1 +set @funcArg_sampleDynamic_1 _main_2 +set @funcTramp_sampleDynamic 13 +jump 1 always +set _main_3 @return +print _main_3`, + }, + { + name: "FunctionStatic", + input: `package main + +import ( + "github.com/Vilsol/go-mlog/m" + "github.com/Vilsol/go-mlog/x" +) + +func main() { + print(sampleStatic()) +} + +func sampleStatic() int { + return 9 +}`, + output: `jump 3 always +set @return 9 +set @counter @funcTramp_sampleStatic +set @funcTramp_sampleStatic 5 +jump 1 always +set _main_0 @return +print _main_0`, + }, + { + name: "FunctionVariable", + input: `package main + +import ( + "github.com/Vilsol/go-mlog/m" + "github.com/Vilsol/go-mlog/x" +) + +func main() { + print(sampleVariable()) +} + +func sampleVariable() int { + x := 5 + return x +}`, + output: `jump 4 always +set _sampleVariable_x 5 +set @return _sampleVariable_x +set @counter @funcTramp_sampleVariable +set @funcTramp_sampleVariable 6 +jump 1 always +set _main_0 @return +print _main_0`, + }, + { + name: "FunctionNone", + input: `package main + +import ( + "github.com/Vilsol/go-mlog/m" + "github.com/Vilsol/go-mlog/x" +) + +func main() { + sampleNone() +} + +func sampleNone() { + println("hello") +}`, + output: `jump 4 always +print "hello" +print "\n" +set @counter @funcTramp_sampleNone +set @funcTramp_sampleNone 6 +jump 1 always`, + }, + { + name: "TreeShake", + input: `package main + +func main() { + hello() +} + +func hello() { + println("hello") +} + +func foo() { + println("foo") +} + +func bar() { + println("bar") +}`, + output: `jump 4 always +print "hello" +print "\n" +set @counter @funcTramp_hello +set @funcTramp_hello 6 +jump 1 always`, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mlog, err := transpiler.GolangToMLOG(test.input, transpiler.Options{}) + + if err != nil { + t.Error(err) + return + } + + assert.Equal(t, test.output, strings.Trim(mlog, "\n")) + }) + } +} diff --git a/tests/statement_test.go b/tests/statement_test.go index 45bec3d..ea9f895 100644 --- a/tests/statement_test.go +++ b/tests/statement_test.go @@ -39,10 +39,11 @@ 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 +jump 3 lessThan _main_i 10 +jump 6 always print _main_i op add _main_i _main_i 1 -jump 2 lessThan _main_i 10`, +jump 3 lessThan _main_i 10`, }, { name: "Reassignment", @@ -63,29 +64,31 @@ jump 2 lessThan _main_i 10`, 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 +jump 3 lessThan _main_i 10 +jump 11 always op equal _main_0 _main_i 5 -jump 5 equal _main_0 1 -jump 6 always -jump 10 always +jump 6 equal _main_0 1 +jump 7 always +jump 11 always print _main_i print "\n" op add _main_i _main_i 1 -jump 2 lessThan _main_i 10`, +jump 3 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 +jump 3 lessThan _main_i 10 +jump 11 always op equal _main_0 _main_i 5 -jump 5 equal _main_0 1 -jump 6 always -jump 8 always +jump 6 equal _main_0 1 +jump 7 always +jump 9 always print _main_i print "\n" op add _main_i _main_i 1 -jump 2 lessThan _main_i 10`, +jump 3 lessThan _main_i 10`, }, { name: "Switch", diff --git a/tests/unit_test.go b/tests/unit_test.go index 0e5c85b..86649a9 100644 --- a/tests/unit_test.go +++ b/tests/unit_test.go @@ -16,7 +16,7 @@ func TestUnit(t *testing.T) { { name: "UnitBind", input: TestMain(`m.UnitBind("A")`), - output: `ubind "A"`, + output: `ubind A`, }, { name: "UnitRadar", diff --git a/transpiler/expression.go b/transpiler/expression.go index 2400fb7..d34e61a 100644 --- a/transpiler/expression.go +++ b/transpiler/expression.go @@ -109,73 +109,11 @@ func callExprToMLOG(ctx context.Context, callExpr *ast.CallExpr, ident []Resolva Variables: ident, }) } else { - for _, arg := range callExpr.Args { - results = append(results, &MLOGStackWriter{ - Action: "add", - }) - - value, leftExprInstructions, err := exprToResolvable(ctx, arg) - if err != nil { - return nil, err - } - results = append(results, leftExprInstructions...) - - results = append(results, &MLOG{ - Comment: "Write argument to memory", - Statement: [][]Resolvable{ - { - &Value{Value: "write"}, - value, - &Value{Value: StackCellName}, - &Value{Value: stackVariable}, - }, - }, - }) - } - - results = append(results, &MLOGStackWriter{ - Action: "add", - }) - - extra := 2 - if ctx.Value(contextOptions).(Options).Debug { - extra += debugCount - } - - results = append(results, &MLOGTrampoline{ - Variable: stackVariable, - Extra: extra, + results = append(results, &MLOGCustomFunction{ + Arguments: callExpr.Args, + Variables: ident, + FunctionName: funcName, }) - - results = append(results, &MLOGJump{ - MLOG: MLOG{ - Comment: "Jump to function: " + funcName, - }, - Condition: []Resolvable{ - &Value{Value: "always"}, - }, - JumpTarget: &FunctionJumpTarget{ - FunctionName: funcName, - }, - }) - - results = append(results, &MLOGStackWriter{ - Action: "sub", - Extra: len(callExpr.Args), - }) - - if len(ident) > 0 { - results = append(results, &MLOG{ - Comment: "Set the variable to the value", - Statement: [][]Resolvable{ - { - &Value{Value: "set"}, - ident[0], - &Value{Value: FunctionReturnVariable}, - }, - }, - }) - } } return results, nil diff --git a/transpiler/main.go b/transpiler/main.go index b9c2d7f..5810a38 100644 --- a/transpiler/main.go +++ b/transpiler/main.go @@ -13,9 +13,8 @@ import ( const stackVariable = `@stack` const FunctionReturnVariable = `@return` -const StackCellName = `bank1` -const debugCellName = `cell2` -const debugCount = 2 +const FunctionArgumentPrefix = `@funcArg_` +const FunctionTrampolinePrefix = `@funcTramp_` const mainFuncName = `main` @@ -98,6 +97,7 @@ func GolangToMLOG(input string, options Options) (string, error) { continue } + prevArgs := 0 for i, param := range castDecl.Type.Params.List { if paramTypeIdent, ok := param.Type.(*ast.Ident); ok { if paramTypeIdent.Name != "int" && paramTypeIdent.Name != "float64" { @@ -111,39 +111,59 @@ func GolangToMLOG(input string, options Options) (string, error) { dVar := &DynamicVariable{} - for _, name := range param.Names { - statements = append([]MLOGStatement{&MLOG{ - Comment: "Read parameter into variable", - Statement: [][]Resolvable{ - { - &Value{Value: "read"}, - &NormalVariable{Name: name.Name}, - &Value{Value: StackCellName}, - dVar, + if options.Stacked != "" { + for _, name := range param.Names { + statements = append([]MLOGStatement{&MLOG{ + Comment: "Read parameter into variable", + Statement: [][]Resolvable{ + { + &Value{Value: "read"}, + &NormalVariable{Name: name.Name}, + &Value{Value: options.Stacked}, + dVar, + }, }, - }, - }}, statements...) - } + }}, statements...) + } - statements = append([]MLOGStatement{ - &MLOG{ - Comment: "Calculate address of parameter", - Statement: [][]Resolvable{ - { - &Value{Value: "op"}, - &Value{Value: "sub"}, - dVar, - &Value{Value: stackVariable}, - &Value{Value: strconv.Itoa(position)}, + statements = append([]MLOGStatement{ + &MLOG{ + Comment: "Calculate address of parameter", + Statement: [][]Resolvable{ + { + &Value{Value: "op"}, + &Value{Value: "sub"}, + dVar, + &Value{Value: stackVariable}, + &Value{Value: strconv.Itoa(position)}, + }, }, }, - }, - }, statements...) + }, statements...) + } else { + for j, name := range param.Names { + statements = append([]MLOGStatement{&MLOG{ + Comment: "Read parameter into variable", + Statement: [][]Resolvable{ + { + &Value{Value: "set"}, + &NormalVariable{Name: name.Name}, + &Value{Value: FunctionArgumentPrefix + castDecl.Name.Name + "_" + strconv.Itoa(prevArgs+j)}, + }, + }, + }}, statements...) + } + } + + prevArgs += len(param.Names) } lastStatement := statements[len(statements)-1] if _, ok := lastStatement.(*MLOGTrampolineBack); !ok { - statements = append(statements, &MLOGTrampolineBack{}) + statements = append(statements, &MLOGTrampolineBack{ + Stacked: options.Stacked, + Function: castDecl.Name.Name, + }) } global.Functions = append(global.Functions, &Function{ @@ -174,8 +194,9 @@ func GolangToMLOG(input string, options Options) (string, error) { ArgumentCount: len(mainFunc.Type.Params.List), }) - startup := []MLOGStatement{ - &MLOG{ + var startup []MLOGStatement + if options.Stacked != "" { + startup = append(startup, &MLOG{ Comment: "Reset Stack", Statement: [][]Resolvable{ { @@ -184,7 +205,7 @@ func GolangToMLOG(input string, options Options) (string, error) { &Value{Value: "0"}, }, }, - }, + }) } global.Constants = make(map[string]bool) @@ -241,35 +262,6 @@ func GolangToMLOG(input string, options Options) (string, error) { startup = make([]MLOGStatement, 0) } - debugWriter := []MLOGAble{ - &MLOG{ - Comment: "Debug", - Statement: [][]Resolvable{ - { - &Value{Value: "write"}, - &Value{Value: "@counter"}, - &Value{Value: debugCellName}, - &Value{Value: "0"}, - }, - }, - }, - &MLOG{ - Comment: "Debug", - Statement: [][]Resolvable{ - { - &Value{Value: "write"}, - &Value{Value: stackVariable}, - &Value{Value: debugCellName}, - &Value{Value: "1"}, - }, - }, - }, - } - - if len(debugWriter) != debugCount { - panic("debugWriter count != debugCount") - } - for _, statement := range startup { if err := statement.PreProcess(context.WithValue(ctx, contextFunction, mainFunc), global, nil); err != nil { return "", err @@ -291,10 +283,6 @@ func GolangToMLOG(input string, options Options) (string, error) { } for _, statement := range fn.Statements { - if options.Debug { - position += debugCount - } - position += statement.SetPosition(position) } } @@ -339,14 +327,6 @@ func GolangToMLOG(input string, options Options) (string, error) { } for _, statement := range fn.Statements { - if options.Debug { - for _, debugStatement := range debugWriter { - deb := debugStatement.ToMLOG() - result += MLOGToString(context.WithValue(ctx, contextFunction, fn.Declaration), deb, debugStatement, lineNumber) - lineNumber += len(deb) - } - } - statements := statement.ToMLOG() result += MLOGToString(context.WithValue(ctx, contextFunction, fn.Declaration), statements, statement, lineNumber) lineNumber += len(statements) diff --git a/transpiler/options.go b/transpiler/options.go index 75870f9..9511cf7 100644 --- a/transpiler/options.go +++ b/transpiler/options.go @@ -3,6 +3,6 @@ package transpiler type Options struct { Numbers bool Comments bool - Debug bool NoStartup bool + Stacked string } diff --git a/transpiler/statement.go b/transpiler/statement.go index 424b032..1cbbad6 100644 --- a/transpiler/statement.go +++ b/transpiler/statement.go @@ -128,7 +128,10 @@ func returnStmtToMLOG(ctx context.Context, statement *ast.ReturnStmt) ([]MLOGSta }) } - return append(results, &MLOGTrampolineBack{}), nil + return append(results, &MLOGTrampolineBack{ + Stacked: ctx.Value(contextOptions).(Options).Stacked, + Function: ctx.Value(contextFunction).(*ast.FuncDecl).Name.Name, + }), nil } func ifStmtToMLOG(ctx context.Context, statement *ast.IfStmt) ([]MLOGStatement, error) { @@ -232,6 +235,7 @@ func forStmtToMLOG(ctx context.Context, statement *ast.ForStmt) ([]MLOGStatement results = append(results, initMlog...) var loopStartJump *MLOGJump + var intoLoopJump *MLOGJump var loopEndJump *MLOGJump if binaryExpr, ok := statement.Cond.(*ast.BinaryExpr); ok { if translatedOp, ok := jumpOperators[binaryExpr.Op]; ok { @@ -259,15 +263,24 @@ func forStmtToMLOG(ctx context.Context, statement *ast.ForStmt) ([]MLOGStatement }, } - loopEndJump = &MLOGJump{ + intoLoopJump = &MLOGJump{ MLOG: MLOG{ - Comment: "Jump to end of loop", + Comment: "Jump into the loop", }, Condition: []Resolvable{ &Value{Value: translatedOp}, leftSide, rightSide, }, + } + + loopEndJump = &MLOGJump{ + MLOG: MLOG{ + Comment: "Jump to end of loop", + }, + Condition: []Resolvable{ + &Value{Value: "always"}, + }, JumpTarget: &StatementJumpTarget{ Statement: loopStartJump, After: true, @@ -287,6 +300,9 @@ func forStmtToMLOG(ctx context.Context, statement *ast.ForStmt) ([]MLOGStatement } blockCtxStruct.Statements = bodyMLOG + intoLoopJump.JumpTarget = bodyMLOG[0] + results = append(results, intoLoopJump) + results = append(results, loopEndJump) results = append(results, bodyMLOG...) diff --git a/transpiler/translations_native.go b/transpiler/translations_native.go index 03bfa5d..bacbf7c 100644 --- a/transpiler/translations_native.go +++ b/transpiler/translations_native.go @@ -60,4 +60,25 @@ func init() { return results, nil }, }) + // TODO Optimize + basicSet := Translator{ + Count: func(args []Resolvable, vars []Resolvable) int { + return 1 + }, + Variables: 1, + Translate: func(args []Resolvable, vars []Resolvable) ([]MLOGStatement, error) { + return []MLOGStatement{ + &MLOG{ + Statement: [][]Resolvable{ + { + &Value{Value: "set"}, + vars[0], + args[0], + }, + }, + }, + }, nil + }, + } + RegisterFuncTranslation("float64", basicSet) } diff --git a/transpiler/type_base.go b/transpiler/type_base.go index 15c0c08..6a53169 100644 --- a/transpiler/type_base.go +++ b/transpiler/type_base.go @@ -21,7 +21,7 @@ type Function struct { type MLOGAble interface { ToMLOG() [][]Resolvable - GetComment() string + GetComment(int) string } type Processable interface { diff --git a/transpiler/type_branch.go b/transpiler/type_branch.go index 205b702..3e0b38b 100644 --- a/transpiler/type_branch.go +++ b/transpiler/type_branch.go @@ -29,6 +29,6 @@ func (m *MLOGBranch) Size() int { return 1 } -func (m *MLOGBranch) GetComment() string { +func (m *MLOGBranch) GetComment(int) string { return "Branch: " + m.Token.String() } diff --git a/transpiler/type_function.go b/transpiler/type_function.go new file mode 100644 index 0000000..5dcc8da --- /dev/null +++ b/transpiler/type_function.go @@ -0,0 +1,162 @@ +package transpiler + +import ( + "context" + "go/ast" + "strconv" +) + +type MLOGCustomFunction struct { + Position int + Arguments []ast.Expr + Variables []Resolvable + Unresolved []MLOGStatement + FunctionName string + Comments map[int]string +} + +func (m *MLOGCustomFunction) ToMLOG() [][]Resolvable { + results := make([][]Resolvable, 0) + m.Comments = make(map[int]string) + for _, statement := range m.Unresolved { + lines := statement.ToMLOG() + results = append(results, lines...) + for i := statement.GetPosition(); i < statement.GetPosition()+len(lines); i++ { + m.Comments[i] = statement.GetComment(i) + } + } + return results +} + +func (m *MLOGCustomFunction) GetPosition() int { + return m.Position +} + +func (m *MLOGCustomFunction) Size() int { + size := 0 + for _, statement := range m.Unresolved { + size += statement.Size() + } + return size +} + +func (m *MLOGCustomFunction) SetPosition(position int) int { + m.Position = position + size := 0 + for _, statement := range m.Unresolved { + size += statement.SetPosition(size + m.Position) + } + return size +} + +func (m *MLOGCustomFunction) PreProcess(ctx context.Context, global *Global, function *Function) error { + if len(m.Unresolved) > 0 { + return nil + } + + stacked := ctx.Value(contextOptions).(Options).Stacked + + for i, arg := range m.Arguments { + value, argInstructions, err := exprToResolvable(ctx, arg) + if err != nil { + return err + } + m.Unresolved = append(m.Unresolved, argInstructions...) + + if stacked != "" { + m.Unresolved = append(m.Unresolved, &MLOGStackWriter{ + Action: "add", + }) + + m.Unresolved = append(m.Unresolved, &MLOG{ + Comment: "Write argument to memory", + Statement: [][]Resolvable{ + { + &Value{Value: "write"}, + value, + &Value{Value: stacked}, + &Value{Value: stackVariable}, + }, + }, + }) + } else { + argNum := strconv.Itoa(i) + m.Unresolved = append(m.Unresolved, &MLOG{ + Comment: "Set " + m.FunctionName + " argument: " + argNum, + Statement: [][]Resolvable{ + { + &Value{Value: "set"}, + &Value{Value: FunctionArgumentPrefix + m.FunctionName + "_" + argNum}, + value, + }, + }, + }) + } + } + + if stacked != "" { + m.Unresolved = append(m.Unresolved, &MLOGStackWriter{ + Action: "add", + }) + } + + m.Unresolved = append(m.Unresolved, &MLOGTrampoline{ + Variable: stackVariable, + Extra: 2, + Stacked: stacked, + Function: m.FunctionName, + }) + + m.Unresolved = append(m.Unresolved, &MLOGJump{ + MLOG: MLOG{ + Comment: "Jump to function: " + m.FunctionName, + }, + Condition: []Resolvable{ + &Value{Value: "always"}, + }, + JumpTarget: &FunctionJumpTarget{ + FunctionName: m.FunctionName, + }, + }) + + if stacked != "" { + m.Unresolved = append(m.Unresolved, &MLOGStackWriter{ + Action: "sub", + Extra: len(m.Arguments), + }) + } + + if len(m.Variables) > 0 { + m.Unresolved = append(m.Unresolved, &MLOG{ + Comment: "Set variable to returned value", + Statement: [][]Resolvable{ + { + &Value{Value: "set"}, + m.Variables[0], + &Value{Value: FunctionReturnVariable}, + }, + }, + }) + } + + for _, statement := range m.Unresolved { + if err := statement.PreProcess(ctx, global, function); err != nil { + return err + } + } + + return nil +} +func (m *MLOGCustomFunction) PostProcess(ctx context.Context, global *Global, function *Function) error { + for i, statement := range m.Unresolved { + statement.SetPosition(m.Position + i) + if err := statement.PostProcess(ctx, global, function); err != nil { + return err + } + } + return nil +} + +func (m *MLOGCustomFunction) GetComment(pos int) string { + return m.Comments[pos] +} diff --git a/transpiler/type_jump.go b/transpiler/type_jump.go index 74310df..c188137 100644 --- a/transpiler/type_jump.go +++ b/transpiler/type_jump.go @@ -42,7 +42,7 @@ func (m *MLOGJump) PostProcess(ctx context.Context, global *Global, function *Fu return m.JumpTarget.PostProcess(ctx, global, function) } -func (m *MLOGJump) GetComment() string { +func (m *MLOGJump) GetComment(int) string { if m.Comment == "" { return "Jump to target" } @@ -100,7 +100,3 @@ func (m *StatementJumpTarget) PreProcess(context.Context, *Global, *Function) er func (m *StatementJumpTarget) PostProcess(context.Context, *Global, *Function) error { return nil } - -type MLOGTrampolineBack struct { - MLOG -} diff --git a/transpiler/type_mlog.go b/transpiler/type_mlog.go index 48b9bbf..a658029 100644 --- a/transpiler/type_mlog.go +++ b/transpiler/type_mlog.go @@ -55,7 +55,7 @@ func (m *MLOG) SetPosition(position int) int { return 1 } -func (m *MLOG) GetComment() string { +func (m *MLOG) GetComment(int) string { return m.Comment } @@ -129,7 +129,7 @@ func (m *MLOGFunc) PostProcess(ctx context.Context, global *Global, function *Fu return nil } -func (m *MLOGFunc) GetComment() string { +func (m *MLOGFunc) GetComment(int) string { return "Call to native function" } @@ -137,19 +137,31 @@ type MLOGTrampoline struct { MLOG Variable string Extra int + Stacked string + Function string } func (m *MLOGTrampoline) ToMLOG() [][]Resolvable { + if m.Stacked != "" { + return [][]Resolvable{ + { + &Value{Value: "write"}, + &Value{Value: strconv.Itoa(m.Position + m.Extra)}, + &Value{Value: m.Stacked}, + &Value{Value: m.Variable}, + }, + } + } + return [][]Resolvable{ { - &Value{Value: "write"}, + &Value{Value: "set"}, + &Value{Value: FunctionTrampolinePrefix + m.Function}, &Value{Value: strconv.Itoa(m.Position + m.Extra)}, - &Value{Value: StackCellName}, - &Value{Value: m.Variable}, }, } } -func (m *MLOGTrampoline) GetComment() string { +func (m *MLOGTrampoline) GetComment(int) string { return "Set Trampoline Address" } diff --git a/transpiler/type_native.go b/transpiler/type_native.go index 7d7e7b3..121effb 100644 --- a/transpiler/type_native.go +++ b/transpiler/type_native.go @@ -20,21 +20,37 @@ func (m *MLOGStackWriter) ToMLOG() [][]Resolvable { } } -func (m *MLOGStackWriter) GetComment() string { +func (m *MLOGStackWriter) GetComment(int) string { return "Update Stack Pointer" } +type MLOGTrampolineBack struct { + MLOG + Stacked string + Function string +} + func (m *MLOGTrampolineBack) ToMLOG() [][]Resolvable { + if m.Stacked != "" { + return [][]Resolvable{ + { + &Value{Value: "read"}, + &Value{Value: "@counter"}, + &Value{Value: m.Stacked}, + &Value{Value: stackVariable}, + }, + } + } + return [][]Resolvable{ { - &Value{Value: "read"}, + &Value{Value: "set"}, &Value{Value: "@counter"}, - &Value{Value: StackCellName}, - &Value{Value: stackVariable}, + &Value{Value: FunctionTrampolinePrefix + m.Function}, }, } } -func (m *MLOGTrampolineBack) GetComment() string { +func (m *MLOGTrampolineBack) GetComment(int) string { return "Trampoline back" } diff --git a/transpiler/type_resolvable.go b/transpiler/type_resolvable.go index 7d66eae..2af2971 100644 --- a/transpiler/type_resolvable.go +++ b/transpiler/type_resolvable.go @@ -32,10 +32,14 @@ type NormalVariable struct { func (m *NormalVariable) PreProcess(_ context.Context, global *Global, function *Function) error { if m.CalculatedName == "" { - if _, ok := global.Constants[m.Name]; ok { - m.CalculatedName = m.Name + if m.Name == "_" { + m.CalculatedName = "@_" } else { - m.CalculatedName = "_" + function.Name + "_" + m.Name + if _, ok := global.Constants[m.Name]; ok { + m.CalculatedName = m.Name + } else { + m.CalculatedName = "_" + function.Name + "_" + m.Name + } } } return nil diff --git a/transpiler/types.go b/transpiler/types.go index 58803f1..e13a59a 100644 --- a/transpiler/types.go +++ b/transpiler/types.go @@ -22,7 +22,7 @@ func MLOGToString(ctx context.Context, statements [][]Resolvable, statement MLOG if ctx.Value(contextOptions).(Options).Comments { result += fmt.Sprintf("%-45s", resultLine) - result += " // " + statement.GetComment() + result += " // " + statement.GetComment(lineNumber) } else { result += resultLine } diff --git a/wasm/main.go b/wasm/main.go index e5e9e44..1d07e02 100644 --- a/wasm/main.go +++ b/wasm/main.go @@ -26,7 +26,6 @@ func transpileWrapper() js.Func { mlog, err := transpiler.GolangToMLOG(input, transpiler.Options{ Numbers: false, Comments: false, - Debug: false, }) if err != nil { fmt.Printf("error transpiling: %s\n", err)