From a91d432989d1f1733c99739a574455f1c6258a40 Mon Sep 17 00:00:00 2001 From: Vilsol Date: Tue, 29 Dec 2020 18:30:16 +0200 Subject: [PATCH] Major refactor, tree shaking --- README.md | 4 +- tests/function_test.go | 29 ++ tests/options_test.go | 4 +- transpiler/expression.go | 255 ++++++------ transpiler/main.go | 41 +- transpiler/statement.go | 742 +++++++++++++++++----------------- transpiler/type_base.go | 56 +++ transpiler/type_branch.go | 34 ++ transpiler/type_jump.go | 106 +++++ transpiler/type_mlog.go | 155 +++++++ transpiler/type_native.go | 40 ++ transpiler/type_resolvable.go | 77 ++++ transpiler/types.go | 428 -------------------- 13 files changed, 1045 insertions(+), 926 deletions(-) create mode 100644 transpiler/type_base.go create mode 100644 transpiler/type_branch.go create mode 100644 transpiler/type_jump.go create mode 100644 transpiler/type_mlog.go create mode 100644 transpiler/type_native.go create mode 100644 transpiler/type_resolvable.go diff --git a/README.md b/README.md index bae21a3..a2661d3 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ A Web IDE is available [here](https://vilsol.github.io/go-mlog-web/?1) * Binary and Unary math * Function level variable scopes * Contextual errors +* Tree-shaking unused functions +* Multi-pass pre/post-processing ## Roadmap @@ -32,8 +34,6 @@ A Web IDE is available [here](https://vilsol.github.io/go-mlog-web/?1) * Variable argument count functions * Multiple function return values * Optimize simple jump instructions -* Multi-pass post-processing -* Tree-shake unused functions ## Design Limitations diff --git a/tests/function_test.go b/tests/function_test.go index f92b116..1f502d2 100644 --- a/tests/function_test.go +++ b/tests/function_test.go @@ -132,6 +132,35 @@ read @counter bank1 @stack op add @stack @stack 1 write 8 bank1 @stack jump 2 always +op sub @stack @stack 1`, + }, + { + name: "TreeShake", + input: `package main + +func main() { + hello() +} + +func hello() { + println("hello") +} + +func foo() { + println("foo") +} + +func bar() { + println("bar") +}`, + output: `set @stack 0 +jump 5 always +print "hello" +print "\n" +read @counter bank1 @stack +op add @stack @stack 1 +write 8 bank1 @stack +jump 2 always op sub @stack @stack 1`, }, } diff --git a/tests/options_test.go b/tests/options_test.go index 1bae069..31d97b9 100644 --- a/tests/options_test.go +++ b/tests/options_test.go @@ -142,7 +142,7 @@ 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 +op add _main_i _main_i 1 // Execute increment/decrement jump 9 lessThan _main_i 10 // Jump to start of loop`, }, { @@ -174,7 +174,7 @@ jump 9 lessThan _main_i 10 // Jump to start of loop`, 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 + 18: op add _main_i _main_i 1 // Execute increment/decrement 19: jump 9 lessThan _main_i 10 // Jump to start of loop`, }, } diff --git a/transpiler/expression.go b/transpiler/expression.go index 4914586..2400fb7 100644 --- a/transpiler/expression.go +++ b/transpiler/expression.go @@ -11,137 +11,23 @@ import ( func expressionToMLOG(ctx context.Context, ident []Resolvable, expr ast.Expr) ([]MLOGStatement, error) { switch castExpr := expr.(type) { case *ast.BasicLit: - value := castExpr.Value - if castExpr.Kind == token.CHAR { - value = "\"" + strings.Trim(value, "'") + "\"" - } - - return []MLOGStatement{&MLOG{ - Comment: "Set the variable to the value", - Statement: [][]Resolvable{ - { - &Value{Value: "set"}, - ident[0], - &Value{Value: value}, - }, - }, - }}, nil + return basicLitToMLOG(ctx, ident, castExpr) case *ast.Ident: - 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: castExpr.Name}, - }, - }, - }}, nil - } - - return []MLOGStatement{&MLOG{ - Comment: "Set the variable to the value of other variable", - Statement: [][]Resolvable{ - { - &Value{Value: "set"}, - ident[0], - &NormalVariable{Name: castExpr.Name}, - }, - }, - }}, nil + return identToMLOG(ctx, ident, castExpr) case *ast.BinaryExpr: - if opTranslated, ok := regularOperators[castExpr.Op]; ok { - instructions := make([]MLOGStatement, 0) - - leftSide, leftExprInstructions, err := exprToResolvable(ctx, castExpr.X) - if err != nil { - return nil, err - } - instructions = append(instructions, leftExprInstructions...) - - rightSide, rightExprInstructions, err := exprToResolvable(ctx, castExpr.Y) - if err != nil { - return nil, err - } - instructions = append(instructions, rightExprInstructions...) - - return append(instructions, &MLOG{ - Comment: "Execute operation", - Statement: [][]Resolvable{ - { - &Value{Value: "op"}, - &Value{Value: opTranslated}, - ident[0], - leftSide, - rightSide, - }, - }, - }), nil - } else { - return nil, Err(ctx, fmt.Sprintf("operator statement cannot use this operation: %s", castExpr.Op.String())) - } + return binaryExprToMLOG(ctx, ident, castExpr) case *ast.CallExpr: - callInstructions, err := callExprToMLOG(ctx, castExpr, ident) - if err != nil { - return nil, err - } - return callInstructions, err + return callExprToMLOG(ctx, castExpr, ident) case *ast.UnaryExpr: - if _, ok := regularOperators[castExpr.Op]; ok { - instructions := make([]MLOGStatement, 0) - - x, exprInstructions, err := exprToResolvable(ctx, castExpr.X) - if err != nil { - return nil, err - } - instructions = append(instructions, exprInstructions...) - - var statement []Resolvable - switch castExpr.Op { - case token.NOT: - statement = []Resolvable{ - &Value{Value: "op"}, - &Value{Value: regularOperators[token.NOT]}, - ident[0], - x, - &Value{Value: "-1"}, - } - break - case token.SUB: - statement = []Resolvable{ - &Value{Value: "op"}, - &Value{Value: regularOperators[token.MUL]}, - ident[0], - x, - &Value{Value: "-1"}, - } - break - } - - if statement == nil { - return nil, Err(ctx, fmt.Sprintf("unsupported unary operation: %s", castExpr.Op.String())) - } - - return append(instructions, &MLOG{ - Comment: "Execute unary operation", - Statement: [][]Resolvable{statement}, - }), nil - } else { - return nil, Err(ctx, fmt.Sprintf("operator statement cannot use this operation: %s", castExpr.Op.String())) - } + return unaryExprToMLOG(ctx, ident, castExpr) case *ast.ParenExpr: - instructions, err := expressionToMLOG(ctx, ident, castExpr.X) - if err != nil { - return nil, err - } - return instructions, nil + return expressionToMLOG(ctx, ident, castExpr.X) case *ast.SelectorExpr: mlog, _, err := selectorExprToMLOG(ctx, ident[0], castExpr) return mlog, err - default: - return nil, Err(ctx, fmt.Sprintf("unsupported expression type: %T", expr)) } + + return nil, Err(ctx, fmt.Sprintf("unsupported expression type: %T", expr)) } func exprToResolvable(ctx context.Context, expr ast.Expr) (Resolvable, []MLOGStatement, error) { @@ -163,10 +49,9 @@ func exprToResolvable(ctx context.Context, expr ast.Expr) (Resolvable, []MLOGSta } return dVar, exprInstructions, nil - default: - return nil, nil, Err(ctx, fmt.Sprintf("unknown resolvable expression type: %T", expr)) } + return nil, nil, Err(ctx, fmt.Sprintf("unknown resolvable expression type: %T", expr)) } func selectorExprToMLOG(ctx context.Context, ident Resolvable, selectorExpr *ast.SelectorExpr) ([]MLOGStatement, string, error) { @@ -323,3 +208,125 @@ func argumentsToResolvables(ctx context.Context, args []ast.Expr) ([]Resolvable, return result, instructions, nil } + +func unaryExprToMLOG(ctx context.Context, ident []Resolvable, expr *ast.UnaryExpr) ([]MLOGStatement, error) { + if _, ok := regularOperators[expr.Op]; ok { + instructions := make([]MLOGStatement, 0) + + x, exprInstructions, err := exprToResolvable(ctx, expr.X) + if err != nil { + return nil, err + } + instructions = append(instructions, exprInstructions...) + + var statement []Resolvable + switch expr.Op { + case token.NOT: + statement = []Resolvable{ + &Value{Value: "op"}, + &Value{Value: regularOperators[token.NOT]}, + ident[0], + x, + &Value{Value: "-1"}, + } + break + case token.SUB: + statement = []Resolvable{ + &Value{Value: "op"}, + &Value{Value: regularOperators[token.MUL]}, + ident[0], + x, + &Value{Value: "-1"}, + } + break + } + + if statement == nil { + return nil, Err(ctx, fmt.Sprintf("unsupported unary operation: %s", expr.Op.String())) + } + + return append(instructions, &MLOG{ + Comment: "Execute unary operation", + Statement: [][]Resolvable{statement}, + }), nil + } + + return nil, Err(ctx, fmt.Sprintf("operator statement cannot use this operation: %s", expr.Op.String())) +} + +func binaryExprToMLOG(ctx context.Context, ident []Resolvable, expr *ast.BinaryExpr) ([]MLOGStatement, error) { + if opTranslated, ok := regularOperators[expr.Op]; ok { + instructions := make([]MLOGStatement, 0) + + leftSide, leftExprInstructions, err := exprToResolvable(ctx, expr.X) + if err != nil { + return nil, err + } + instructions = append(instructions, leftExprInstructions...) + + rightSide, rightExprInstructions, err := exprToResolvable(ctx, expr.Y) + if err != nil { + return nil, err + } + instructions = append(instructions, rightExprInstructions...) + + return append(instructions, &MLOG{ + Comment: "Execute operation", + Statement: [][]Resolvable{ + { + &Value{Value: "op"}, + &Value{Value: opTranslated}, + ident[0], + leftSide, + rightSide, + }, + }, + }), nil + } + + return nil, Err(ctx, fmt.Sprintf("operator statement cannot use this operation: %s", expr.Op.String())) +} + +func identToMLOG(_ context.Context, ident []Resolvable, expr *ast.Ident) ([]MLOGStatement, error) { + if expr.Name == "true" || expr.Name == "false" { + return []MLOGStatement{&MLOG{ + Comment: "Set the variable to the value", + Statement: [][]Resolvable{ + { + &Value{Value: "set"}, + ident[0], + &Value{Value: expr.Name}, + }, + }, + }}, nil + } + + return []MLOGStatement{&MLOG{ + Comment: "Set the variable to the value of other variable", + Statement: [][]Resolvable{ + { + &Value{Value: "set"}, + ident[0], + &NormalVariable{Name: expr.Name}, + }, + }, + }}, nil +} + +func basicLitToMLOG(_ context.Context, ident []Resolvable, expr *ast.BasicLit) ([]MLOGStatement, error) { + value := expr.Value + if expr.Kind == token.CHAR { + value = "\"" + strings.Trim(value, "'") + "\"" + } + + return []MLOGStatement{&MLOG{ + Comment: "Set the variable to the value", + Statement: [][]Resolvable{ + { + &Value{Value: "set"}, + ident[0], + &Value{Value: value}, + }, + }, + }}, nil +} diff --git a/transpiler/main.go b/transpiler/main.go index 4533d49..b9c2d7f 100644 --- a/transpiler/main.go +++ b/transpiler/main.go @@ -17,6 +17,8 @@ const StackCellName = `bank1` const debugCellName = `cell2` const debugCount = 2 +const mainFuncName = `main` + func GolangToMLOGFile(fileName string, options Options) (string, error) { file, err := ioutil.ReadFile(fileName) @@ -56,7 +58,7 @@ func GolangToMLOG(input string, options Options) (string, error) { for _, decl := range f.Decls { switch castDecl := decl.(type) { case *ast.FuncDecl: - if castDecl.Name.Name == "main" { + if castDecl.Name.Name == mainFuncName { mainFunc = castDecl } break @@ -83,7 +85,7 @@ func GolangToMLOG(input string, options Options) (string, error) { for _, decl := range f.Decls { switch castDecl := decl.(type) { case *ast.FuncDecl: - if castDecl.Name.Name == "main" { + if castDecl.Name.Name == mainFuncName { continue } fnCtx := context.WithValue(ctx, contextFunction, castDecl) @@ -92,6 +94,10 @@ func GolangToMLOG(input string, options Options) (string, error) { return "", err } + if len(statements) == 0 { + continue + } + for i, param := range castDecl.Type.Params.List { if paramTypeIdent, ok := param.Type.(*ast.Ident); ok { if paramTypeIdent.Name != "int" && paramTypeIdent.Name != "float64" { @@ -161,7 +167,8 @@ func GolangToMLOG(input string, options Options) (string, error) { } global.Functions = append(global.Functions, &Function{ - Name: mainFunc.Name.Name, + Name: mainFuncName, + Called: true, Declaration: mainFunc, Statements: mainStatements, ArgumentCount: len(mainFunc.Type.Params.List), @@ -226,7 +233,7 @@ func GolangToMLOG(input string, options Options) (string, error) { &Value{Value: "always"}, }, JumpTarget: &FunctionJumpTarget{ - FunctionName: "main", + FunctionName: mainFuncName, }, }) @@ -263,8 +270,26 @@ func GolangToMLOG(input string, options Options) (string, error) { panic("debugWriter count != debugCount") } + for _, statement := range startup { + if err := statement.PreProcess(context.WithValue(ctx, contextFunction, mainFunc), global, nil); err != nil { + return "", err + } + } + + for _, fn := range global.Functions { + for _, statement := range fn.Statements { + if err := statement.PreProcess(context.WithValue(ctx, contextFunction, fn.Declaration), global, fn); err != nil { + return "", err + } + } + } + position := len(startup) for _, fn := range global.Functions { + if !fn.Called { + continue + } + for _, statement := range fn.Statements { if options.Debug { position += debugCount @@ -281,6 +306,10 @@ func GolangToMLOG(input string, options Options) (string, error) { } for _, fn := range global.Functions { + if !fn.Called { + continue + } + for _, statement := range fn.Statements { if err := statement.PostProcess(context.WithValue(ctx, contextFunction, fn.Declaration), global, fn); err != nil { return "", err @@ -297,6 +326,10 @@ func GolangToMLOG(input string, options Options) (string, error) { } for _, fn := range global.Functions { + if !fn.Called { + continue + } + if options.Comments { if result != "" { result += "\n" diff --git a/transpiler/statement.go b/transpiler/statement.go index bff5418..424b032 100644 --- a/transpiler/statement.go +++ b/transpiler/statement.go @@ -10,464 +10,474 @@ import ( func statementToMLOG(ctx context.Context, statement ast.Stmt) ([]MLOGStatement, error) { subCtx := context.WithValue(ctx, contextStatement, statement) - results := make([]MLOGStatement, 0) - switch castStmt := statement.(type) { case *ast.ForStmt: - if len(castStmt.Body.List) == 0 { - break - } + return forStmtToMLOG(subCtx, castStmt) + case *ast.ExprStmt: + return expressionToMLOG(subCtx, nil, castStmt.X) + case *ast.IfStmt: + return ifStmtToMLOG(subCtx, castStmt) + case *ast.AssignStmt: + return assignStmtToMLOG(subCtx, castStmt) + case *ast.ReturnStmt: + return returnStmtToMLOG(subCtx, castStmt) + case *ast.BlockStmt: + return blockStmtToMLOG(subCtx, castStmt) + case *ast.IncDecStmt: + return incDecStmtToMLOG(subCtx, castStmt) + case *ast.BranchStmt: + return branchStmtToMLOG(subCtx, castStmt) + case *ast.SwitchStmt: + return switchStmtToMLOG(subCtx, castStmt) + } - initMlog, err := statementToMLOG(subCtx, castStmt.Init) - if err != nil { - return nil, err - } - results = append(results, initMlog...) + return nil, Err(subCtx, fmt.Sprintf("statement type not supported: %T", statement)) +} - var loopStartJump *MLOGJump - var loopEndJump *MLOGJump - if binaryExpr, ok := castStmt.Cond.(*ast.BinaryExpr); ok { - if translatedOp, ok := jumpOperators[binaryExpr.Op]; ok { +func assignStmtToMLOG(ctx context.Context, statement *ast.AssignStmt) ([]MLOGStatement, error) { + mlog := make([]MLOGStatement, 0) - leftSide, leftExprInstructions, err := exprToResolvable(ctx, binaryExpr.X) - if err != nil { - return nil, err - } - results = append(results, leftExprInstructions...) + if len(statement.Lhs) != len(statement.Rhs) { + if len(statement.Rhs) == 1 { + leftSide := make([]Resolvable, len(statement.Lhs)) - rightSide, rightExprInstructions, err := exprToResolvable(ctx, binaryExpr.Y) - if err != nil { - return nil, err + for i, lhs := range statement.Lhs { + leftSide[i] = &NormalVariable{Name: lhs.(*ast.Ident).Name} + } + + exprMLOG, err := expressionToMLOG(ctx, leftSide, statement.Rhs[0]) + if err != nil { + return nil, err + } + mlog = append(mlog, exprMLOG...) + } else { + return nil, Err(ctx, "mismatched variable assignment sides") + } + } else { + for i, expr := range statement.Lhs { + if ident, ok := expr.(*ast.Ident); ok { + nVar := &NormalVariable{Name: ident.Name} + if opTranslated, ok := regularOperators[statement.Tok]; ok { + instructions := make([]MLOGStatement, 0) + + rightSide, rightExprInstructions, err := exprToResolvable(ctx, statement.Rhs[i]) + if err != nil { + return nil, err + } + instructions = append(instructions, rightExprInstructions...) + + return append(instructions, &MLOG{ + Comment: "Execute operation", + Statement: [][]Resolvable{ + { + &Value{Value: "op"}, + &Value{Value: opTranslated}, + nVar, + nVar, + rightSide, + }, + }, + }), nil } - results = append(results, rightExprInstructions...) - loopStartJump = &MLOGJump{ - MLOG: MLOG{ - Comment: "Jump to start of loop", - }, - Condition: []Resolvable{ - &Value{Value: translatedOp}, - leftSide, - rightSide, - }, + if statement.Tok != token.ASSIGN && statement.Tok != token.DEFINE { + return nil, Err(ctx, "only direct assignment is supported") } - loopEndJump = &MLOGJump{ - MLOG: MLOG{ - Comment: "Jump to end of loop", - }, - Condition: []Resolvable{ - &Value{Value: translatedOp}, - leftSide, - rightSide, - }, - JumpTarget: &StatementJumpTarget{ - Statement: loopStartJump, - After: true, - }, + exprMLOG, err := expressionToMLOG(ctx, []Resolvable{nVar}, statement.Rhs[i]) + if err != nil { + return nil, err } + mlog = append(mlog, exprMLOG...) } else { - return nil, Err(subCtx, fmt.Sprintf("jump statement cannot use this operation: %T", binaryExpr.Op)) + return nil, Err(ctx, "left side variable assignment can only contain identifications") } - } else { - return nil, Err(subCtx, "for loop can only have binary conditional expressions") } + } + + return mlog, nil +} + +func returnStmtToMLOG(ctx context.Context, statement *ast.ReturnStmt) ([]MLOGStatement, error) { + if len(statement.Results) > 1 { + // TODO Multi-value returns + return nil, Err(ctx, "only single value returns are supported") + } - blockCtxStruct := &ContextBlock{} - bodyMLOG, err := statementToMLOG(context.WithValue(subCtx, contextBreakableBlock, blockCtxStruct), castStmt.Body) + results := make([]MLOGStatement, 0) + + if len(statement.Results) > 0 { + returnValue := statement.Results[0] + + resultVar, exprInstructions, err := exprToResolvable(ctx, returnValue) if err != nil { return nil, err } - blockCtxStruct.Statements = bodyMLOG - results = append(results, loopEndJump) + results = append(results, exprInstructions...) - results = append(results, bodyMLOG...) + results = append(results, &MLOG{ + Comment: "Set return data", + Statement: [][]Resolvable{ + { + &Value{Value: "set"}, + &Value{Value: FunctionReturnVariable}, + resultVar, + }, + }, + }) + } - instructions, err := statementToMLOG(subCtx, castStmt.Post) + return append(results, &MLOGTrampolineBack{}), nil +} + +func ifStmtToMLOG(ctx context.Context, statement *ast.IfStmt) ([]MLOGStatement, error) { + results := make([]MLOGStatement, 0) + + if statement.Init != nil { + instructions, err := statementToMLOG(ctx, statement.Init) 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) + var condVar Resolvable + if condIdent, ok := statement.Cond.(*ast.Ident); ok { + condVar = &NormalVariable{Name: condIdent.Name} + } else { + condVar = &DynamicVariable{} - break - case *ast.ExprStmt: - instructions, err := expressionToMLOG(subCtx, nil, castStmt.X) + instructions, err := expressionToMLOG(ctx, []Resolvable{condVar}, statement.Cond) if err != nil { return nil, err } results = append(results, instructions...) - break - case *ast.IfStmt: - if castStmt.Init != nil { - instructions, err := statementToMLOG(subCtx, castStmt.Init) - if err != nil { - return nil, err - } - results = append(results, instructions...) - } - - var condVar Resolvable - if condIdent, ok := castStmt.Cond.(*ast.Ident); ok { - condVar = &NormalVariable{Name: condIdent.Name} - } else { - condVar = &DynamicVariable{} - - instructions, err := expressionToMLOG(subCtx, []Resolvable{condVar}, castStmt.Cond) - if err != nil { - return nil, err - } + } - results = append(results, instructions...) - } + blockInstructions, err := statementToMLOG(ctx, statement.Body) + if err != nil { + return nil, err + } - blockInstructions, err := statementToMLOG(subCtx, castStmt.Body) + results = append(results, &MLOGJump{ + MLOG: MLOG{ + Comment: "Jump to if block if true", + }, + Condition: []Resolvable{ + &Value{Value: "equal"}, + condVar, + &Value{Value: "1"}, + }, + JumpTarget: &StatementJumpTarget{ + Statement: blockInstructions[0], + }, + }) + + afterIfTarget := &StatementJumpTarget{ + After: true, + Statement: blockInstructions[len(blockInstructions)-1], + } + results = append(results, &MLOGJump{ + MLOG: MLOG{ + Comment: "Jump to after if block", + }, + Condition: []Resolvable{ + &Value{Value: "always"}, + }, + JumpTarget: afterIfTarget, + }) + + results = append(results, blockInstructions...) + + if statement.Else != nil { + elseInstructions, err := statementToMLOG(ctx, statement.Else) if err != nil { return nil, err } - results = append(results, &MLOGJump{ + afterElseJump := &MLOGJump{ MLOG: MLOG{ - Comment: "Jump to if block if true", + Comment: "Jump to after else block", }, Condition: []Resolvable{ - &Value{Value: "equal"}, - condVar, - &Value{Value: "1"}, + &Value{Value: "always"}, }, JumpTarget: &StatementJumpTarget{ - Statement: blockInstructions[0], + After: true, + Statement: elseInstructions[len(elseInstructions)-1], }, - }) - - afterIfTarget := &StatementJumpTarget{ - After: true, - Statement: blockInstructions[len(blockInstructions)-1], } - results = append(results, &MLOGJump{ - MLOG: MLOG{ - Comment: "Jump to after if block", - }, - Condition: []Resolvable{ - &Value{Value: "always"}, - }, - JumpTarget: afterIfTarget, - }) + results = append(results, afterElseJump) + afterIfTarget.Statement = afterElseJump - results = append(results, blockInstructions...) + results = append(results, elseInstructions...) + } - if castStmt.Else != nil { - elseInstructions, err := statementToMLOG(subCtx, castStmt.Else) - if err != nil { - return nil, err - } + return results, nil +} - afterElseJump := &MLOGJump{ - MLOG: MLOG{ - Comment: "Jump to after else block", - }, - Condition: []Resolvable{ - &Value{Value: "always"}, - }, - JumpTarget: &StatementJumpTarget{ - After: true, - Statement: elseInstructions[len(elseInstructions)-1], - }, - } - results = append(results, afterElseJump) - afterIfTarget.Statement = afterElseJump +func forStmtToMLOG(ctx context.Context, statement *ast.ForStmt) ([]MLOGStatement, error) { + results := make([]MLOGStatement, 0) - results = append(results, elseInstructions...) - } + if len(statement.Body.List) == 0 { + return results, nil + } - break - case *ast.AssignStmt: - assignMlog, err := assignStmtToMLOG(subCtx, castStmt) - if err != nil { - return nil, err - } - results = append(results, assignMlog...) - break - case *ast.ReturnStmt: - if len(castStmt.Results) > 1 { - // TODO Multi-value returns - return nil, Err(subCtx, "only single value returns are supported") - } + initMlog, err := statementToMLOG(ctx, statement.Init) + if err != nil { + return nil, err + } + results = append(results, initMlog...) - if len(castStmt.Results) > 0 { - returnValue := castStmt.Results[0] + var loopStartJump *MLOGJump + var loopEndJump *MLOGJump + if binaryExpr, ok := statement.Cond.(*ast.BinaryExpr); ok { + if translatedOp, ok := jumpOperators[binaryExpr.Op]; ok { - resultVar, exprInstructions, err := exprToResolvable(ctx, returnValue) + leftSide, leftExprInstructions, err := exprToResolvable(ctx, binaryExpr.X) if err != nil { return nil, err } - results = append(results, exprInstructions...) - - results = append(results, &MLOG{ - Comment: "Set return data", - Statement: [][]Resolvable{ - { - &Value{Value: "set"}, - &Value{Value: FunctionReturnVariable}, - resultVar, - }, - }, - }) - } + results = append(results, leftExprInstructions...) - results = append(results, &MLOGTrampolineBack{}) - break - case *ast.BlockStmt: - blockCtxStruct := &ContextBlock{} - statements := make([]MLOGStatement, 0) - for _, s := range castStmt.List { - instructions, err := statementToMLOG(context.WithValue(subCtx, contextBlock, blockCtxStruct), s) + rightSide, rightExprInstructions, err := exprToResolvable(ctx, binaryExpr.Y) if err != nil { return nil, err } - statements = append(statements, instructions...) - } - blockCtxStruct.Statements = statements - results = append(results, statements...) - break - case *ast.IncDecStmt: - name := &NormalVariable{Name: castStmt.X.(*ast.Ident).Name} - op := "add" - if castStmt.Tok == token.DEC { - op = "sub" - } - results = append(results, &MLOG{ - Comment: "Execute for loop post condition increment/decrement", - Statement: [][]Resolvable{ - { - &Value{Value: "op"}, - &Value{Value: op}, - name, - name, - &Value{Value: "1"}, + results = append(results, rightExprInstructions...) + + loopStartJump = &MLOGJump{ + MLOG: MLOG{ + Comment: "Jump to start of loop", + }, + Condition: []Resolvable{ + &Value{Value: translatedOp}, + leftSide, + rightSide, }, - }, - }) - 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...) - } - tag, leftExprInstructions, err := exprToResolvable(ctx, castStmt.Tag) - if err != nil { - return nil, err + 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, Err(ctx, fmt.Sprintf("jump statement cannot use this operation: %T", binaryExpr.Op)) } - results = append(results, leftExprInstructions...) - - 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)) - } + } else { + return nil, Err(ctx, "for loop can only have binary conditional expressions") + } - 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) - } - } + blockCtxStruct := &ContextBlock{} + bodyMLOG, err := statementToMLOG(context.WithValue(ctx, contextBreakableBlock, blockCtxStruct), statement.Body) + if err != nil { + return nil, err + } + blockCtxStruct.Statements = bodyMLOG - 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) - } - } + results = append(results, loopEndJump) - instructions = append(instructions, statements...) + results = append(results, bodyMLOG...) - addJump := len(caseStmt.Body) == 0 - if len(caseStmt.Body) >= 0 { - if _, ok := caseStmt.Body[len(caseStmt.Body)-1].(*ast.BranchStmt); !ok { - addJump = true - } - } + instructions, err := statementToMLOG(ctx, statement.Post) + if err != nil { + return nil, err + } + results = append(results, instructions...) + blockCtxStruct.Extra = append(blockCtxStruct.Extra, instructions...) - if addJump { - endBreak := &MLOGBreak{ - Block: blockCtxStruct, - } - instructions = append(instructions, endBreak) + loopStartJump.JumpTarget = bodyMLOG[0] + results = append(results, loopStartJump) + blockCtxStruct.Extra = append(blockCtxStruct.Extra, loopStartJump) - if skipClause != nil { - skipClause.JumpTarget.(*StatementJumpTarget).Statement = endBreak - } - } + return results, nil +} - previousSwitchClause = switchClauseBlockCtxStruct - } else { - return nil, Err(subCtx, "switch statement may only contain case and default statements") - } +func blockStmtToMLOG(ctx context.Context, statement *ast.BlockStmt) ([]MLOGStatement, error) { + blockCtxStruct := &ContextBlock{} + statements := make([]MLOGStatement, 0) + for _, s := range statement.List { + instructions, err := statementToMLOG(context.WithValue(ctx, contextBlock, blockCtxStruct), s) + if err != nil { + return nil, err } + statements = append(statements, instructions...) + } + blockCtxStruct.Statements = statements - blockCtxStruct.Statements = instructions + return statements, nil +} - results = append(results, instructions...) +func incDecStmtToMLOG(_ context.Context, statement *ast.IncDecStmt) ([]MLOGStatement, error) { + name := &NormalVariable{Name: statement.X.(*ast.Ident).Name} + op := "add" + if statement.Tok == token.DEC { + op = "sub" + } + return []MLOGStatement{&MLOG{ + Comment: "Execute increment/decrement", + Statement: [][]Resolvable{ + { + &Value{Value: "op"}, + &Value{Value: op}, + name, + name, + &Value{Value: "1"}, + }, + }, + }}, nil +} - break - default: - return nil, Err(subCtx, fmt.Sprintf("statement type not supported: %T", statement)) +func branchStmtToMLOG(ctx context.Context, statement *ast.BranchStmt) ([]MLOGStatement, error) { + switch statement.Tok { + case token.BREAK: + fallthrough + case token.CONTINUE: + block := ctx.Value(contextBreakableBlock) + if block == nil { + return nil, Err(ctx, fmt.Sprintf("branch statement outside any breakable block scope")) + } + return []MLOGStatement{&MLOGBranch{ + Block: block.(*ContextBlock), + Token: statement.Tok, + }}, nil + case token.FALLTHROUGH: + block := ctx.Value(contextSwitchClauseBlock) + if block == nil { + return nil, Err(ctx, fmt.Sprintf("fallthrough statement outside switch scope")) + } + return []MLOGStatement{&MLOGBranch{ + Block: block.(*ContextBlock), + Token: statement.Tok, + }}, nil } - return results, nil + return nil, Err(ctx, fmt.Sprintf("branch statement not supported: %s", statement.Tok)) } -func assignStmtToMLOG(ctx context.Context, statement *ast.AssignStmt) ([]MLOGStatement, error) { - mlog := make([]MLOGStatement, 0) +func switchStmtToMLOG(ctx context.Context, statement *ast.SwitchStmt) ([]MLOGStatement, error) { + results := make([]MLOGStatement, 0) - if len(statement.Lhs) != len(statement.Rhs) { - if len(statement.Rhs) == 1 { - leftSide := make([]Resolvable, len(statement.Lhs)) + if statement.Init != nil { + instructions, err := statementToMLOG(ctx, statement.Init) + if err != nil { + return nil, err + } + results = append(results, instructions...) + } - for i, lhs := range statement.Lhs { - leftSide[i] = &NormalVariable{Name: lhs.(*ast.Ident).Name} + tag, leftExprInstructions, err := exprToResolvable(ctx, statement.Tag) + if err != nil { + return nil, err + } + results = append(results, leftExprInstructions...) + + blockCtxStruct := &ContextBlock{} + blockCtx := context.WithValue(ctx, contextBreakableBlock, blockCtxStruct) + instructions := make([]MLOGStatement, 0) + + var previousSwitchClause *ContextBlock + for _, switchStmt := range statement.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(ctx, fmt.Sprintf("unknown switch case condition type: %T", caseExpr)) + } - exprMLOG, err := expressionToMLOG(ctx, leftSide, statement.Rhs[0]) - if err != nil { - return nil, err + 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) + } } - mlog = append(mlog, exprMLOG...) - } else { - return nil, Err(ctx, "mismatched variable assignment sides") - } - } else { - for i, expr := range statement.Lhs { - if ident, ok := expr.(*ast.Ident); ok { - nVar := &NormalVariable{Name: ident.Name} - if opTranslated, ok := regularOperators[statement.Tok]; ok { - instructions := make([]MLOGStatement, 0) - rightSide, rightExprInstructions, err := exprToResolvable(ctx, statement.Rhs[i]) - if err != nil { - return nil, err - } - instructions = append(instructions, rightExprInstructions...) + 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) + } + } - return append(instructions, &MLOG{ - Comment: "Execute operation", - Statement: [][]Resolvable{ - { - &Value{Value: "op"}, - &Value{Value: opTranslated}, - nVar, - nVar, - rightSide, - }, - }, - }), nil + 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 statement.Tok != token.ASSIGN && statement.Tok != token.DEFINE { - return nil, Err(ctx, "only direct assignment is supported") + if addJump { + endBreak := &MLOGBranch{ + Block: blockCtxStruct, } + instructions = append(instructions, endBreak) - exprMLOG, err := expressionToMLOG(ctx, []Resolvable{nVar}, statement.Rhs[i]) - if err != nil { - return nil, err + if skipClause != nil { + skipClause.JumpTarget.(*StatementJumpTarget).Statement = endBreak } - mlog = append(mlog, exprMLOG...) - } else { - return nil, Err(ctx, "left side variable assignment can only contain identifications") } + + previousSwitchClause = switchClauseBlockCtxStruct + } else { + return nil, Err(ctx, "switch statement may only contain case and default statements") } } - return mlog, nil + blockCtxStruct.Statements = instructions + + return append(results, instructions...), nil } diff --git a/transpiler/type_base.go b/transpiler/type_base.go new file mode 100644 index 0000000..15c0c08 --- /dev/null +++ b/transpiler/type_base.go @@ -0,0 +1,56 @@ +package transpiler + +import ( + "context" + "go/ast" +) + +type Global struct { + Functions []*Function + Constants map[string]bool +} + +type Function struct { + Name string + Called bool + Declaration *ast.FuncDecl + Statements []MLOGStatement + ArgumentCount int + VariableCounter int +} + +type MLOGAble interface { + ToMLOG() [][]Resolvable + GetComment() string +} + +type Processable interface { + PreProcess(context.Context, *Global, *Function) error + PostProcess(context.Context, *Global, *Function) error +} + +type WithPosition interface { + GetPosition() int + Size() int +} + +type MutablePosition interface { + SetPosition(int) int +} + +type MLOGStatement interface { + MLOGAble + WithPosition + MutablePosition + Processable +} + +type JumpTarget interface { + Processable + WithPosition +} + +type Resolvable interface { + Processable + GetValue() string +} diff --git a/transpiler/type_branch.go b/transpiler/type_branch.go new file mode 100644 index 0000000..205b702 --- /dev/null +++ b/transpiler/type_branch.go @@ -0,0 +1,34 @@ +package transpiler + +import ( + "go/token" + "strconv" +) + +type MLOGBranch struct { + MLOG + Block *ContextBlock + Token token.Token +} + +func (m *MLOGBranch) ToMLOG() [][]Resolvable { + lastStatement := m.Block.Statements[len(m.Block.Statements)-1] + if m.Token != token.CONTINUE && 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 *MLOGBranch) Size() int { + return 1 +} + +func (m *MLOGBranch) GetComment() string { + return "Branch: " + m.Token.String() +} diff --git a/transpiler/type_jump.go b/transpiler/type_jump.go new file mode 100644 index 0000000..74310df --- /dev/null +++ b/transpiler/type_jump.go @@ -0,0 +1,106 @@ +package transpiler + +import ( + "context" + "strconv" +) + +type MLOGJump struct { + MLOG + Condition []Resolvable + JumpTarget JumpTarget +} + +func (m *MLOGJump) ToMLOG() [][]Resolvable { + return [][]Resolvable{ + append([]Resolvable{ + &Value{Value: "jump"}, + &Value{Value: strconv.Itoa(m.JumpTarget.GetPosition())}, + }, m.Condition...), + } +} + +func (m *MLOGJump) Size() int { + return 1 +} + +func (m *MLOGJump) PreProcess(ctx context.Context, global *Global, function *Function) error { + for _, resolvable := range m.Condition { + if err := resolvable.PreProcess(ctx, global, function); err != nil { + return err + } + } + return m.JumpTarget.PreProcess(ctx, global, function) +} + +func (m *MLOGJump) PostProcess(ctx context.Context, global *Global, function *Function) error { + for _, resolvable := range m.Condition { + if err := resolvable.PostProcess(ctx, global, function); err != nil { + return err + } + } + return m.JumpTarget.PostProcess(ctx, global, function) +} + +func (m *MLOGJump) GetComment() string { + if m.Comment == "" { + return "Jump to target" + } + return m.Comment +} + +type FunctionJumpTarget struct { + Statement WithPosition + FunctionName string +} + +func (m *FunctionJumpTarget) GetPosition() int { + return m.Statement.GetPosition() +} + +func (m *FunctionJumpTarget) Size() int { + return 1 +} + +func (m *FunctionJumpTarget) PreProcess(ctx context.Context, global *Global, _ *Function) error { + for _, fn := range global.Functions { + if fn.Name == m.FunctionName { + fn.Called = true + m.Statement = fn.Statements[0] + return nil + } + } + return Err(ctx, "unknown function: "+m.FunctionName) +} + +func (m *FunctionJumpTarget) PostProcess(context.Context, *Global, *Function) error { + return nil +} + +type StatementJumpTarget struct { + Statement WithPosition + After bool +} + +func (m *StatementJumpTarget) GetPosition() int { + if m.After { + return m.Statement.GetPosition() + m.Statement.Size() + } + return m.Statement.GetPosition() +} + +func (m *StatementJumpTarget) Size() int { + return 1 +} + +func (m *StatementJumpTarget) PreProcess(context.Context, *Global, *Function) error { + return nil +} + +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 new file mode 100644 index 0000000..48b9bbf --- /dev/null +++ b/transpiler/type_mlog.go @@ -0,0 +1,155 @@ +package transpiler + +import ( + "context" + "fmt" + "strconv" +) + +type MLOG struct { + Statement [][]Resolvable + Position int + Comment string +} + +func (m *MLOG) ToMLOG() [][]Resolvable { + return m.Statement +} + +func (m *MLOG) PreProcess(ctx context.Context, global *Global, function *Function) error { + for _, resolvables := range m.Statement { + for _, resolvable := range resolvables { + if err := resolvable.PreProcess(ctx, global, function); err != nil { + return err + } + } + } + return nil +} + +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(ctx, global, function); err != nil { + return err + } + } + } + return nil +} + +func (m *MLOG) GetPosition() int { + return m.Position +} + +func (m *MLOG) Size() int { + if len(m.Statement) == 0 { + panic("statement without instructions") + } + + return len(m.Statement) +} + +func (m *MLOG) SetPosition(position int) int { + m.Position = position + return 1 +} + +func (m *MLOG) GetComment() string { + return m.Comment +} + +type MLOGFunc struct { + Position int + Function Translator + Arguments []Resolvable + Variables []Resolvable + Unresolved []MLOGStatement +} + +func (m *MLOGFunc) ToMLOG() [][]Resolvable { + results := make([][]Resolvable, 0) + for _, statement := range m.Unresolved { + results = append(results, statement.ToMLOG()...) + } + return results +} + +func (m *MLOGFunc) GetPosition() int { + return m.Position +} + +func (m *MLOGFunc) Size() int { + return m.Function.Count(m.Arguments, m.Variables) +} + +func (m *MLOGFunc) SetPosition(position int) int { + m.Position = position + return m.Function.Count(m.Arguments, m.Variables) +} + +func (m *MLOGFunc) PreProcess(ctx context.Context, global *Global, function *Function) error { + if len(m.Variables) != m.Function.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.PreProcess(ctx, global, function); err != nil { + return err + } + } + + var err error + m.Unresolved, err = m.Function.Translate(m.Arguments, m.Variables) + if err != nil { + return err + } + + for _, statement := range m.Unresolved { + if err := statement.PreProcess(ctx, global, function); err != nil { + return err + } + } + return nil +} + +func (m *MLOGFunc) PostProcess(ctx context.Context, global *Global, function *Function) error { + for _, argument := range m.Arguments { + if err := argument.PostProcess(ctx, global, function); err != nil { + return err + } + } + + 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 *MLOGFunc) GetComment() string { + return "Call to native function" +} + +type MLOGTrampoline struct { + MLOG + Variable string + Extra int +} + +func (m *MLOGTrampoline) ToMLOG() [][]Resolvable { + return [][]Resolvable{ + { + &Value{Value: "write"}, + &Value{Value: strconv.Itoa(m.Position + m.Extra)}, + &Value{Value: StackCellName}, + &Value{Value: m.Variable}, + }, + } +} + +func (m *MLOGTrampoline) GetComment() string { + return "Set Trampoline Address" +} diff --git a/transpiler/type_native.go b/transpiler/type_native.go new file mode 100644 index 0000000..7d7e7b3 --- /dev/null +++ b/transpiler/type_native.go @@ -0,0 +1,40 @@ +package transpiler + +import "strconv" + +type MLOGStackWriter struct { + MLOG + Action string + Extra int +} + +func (m *MLOGStackWriter) ToMLOG() [][]Resolvable { + return [][]Resolvable{ + { + &Value{Value: "op"}, + &Value{Value: m.Action}, + &Value{Value: stackVariable}, + &Value{Value: stackVariable}, + &Value{Value: strconv.Itoa(1 + m.Extra)}, + }, + } +} + +func (m *MLOGStackWriter) GetComment() string { + return "Update Stack Pointer" +} + +func (m *MLOGTrampolineBack) ToMLOG() [][]Resolvable { + return [][]Resolvable{ + { + &Value{Value: "read"}, + &Value{Value: "@counter"}, + &Value{Value: StackCellName}, + &Value{Value: stackVariable}, + }, + } +} + +func (m *MLOGTrampolineBack) GetComment() string { + return "Trampoline back" +} diff --git a/transpiler/type_resolvable.go b/transpiler/type_resolvable.go new file mode 100644 index 0000000..7d66eae --- /dev/null +++ b/transpiler/type_resolvable.go @@ -0,0 +1,77 @@ +package transpiler + +import ( + "context" + "strconv" +) + +type Value struct { + Value string +} + +func (m *Value) GetValue() string { + return m.Value +} + +func (m *Value) PreProcess(context.Context, *Global, *Function) error { + return nil +} + +func (m *Value) PostProcess(context.Context, *Global, *Function) error { + return nil +} + +func (m *Value) String() string { + return m.Value +} + +type NormalVariable struct { + Name string + CalculatedName string +} + +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 + } else { + m.CalculatedName = "_" + function.Name + "_" + m.Name + } + } + return nil +} + +func (m *NormalVariable) PostProcess(context.Context, *Global, *Function) error { + return nil +} + +func (m *NormalVariable) GetValue() string { + if m.CalculatedName == "" { + panic("PreProcess not called on NormalVariable (" + m.Name + ")") + } + return m.CalculatedName +} + +type DynamicVariable struct { + Name string +} + +func (m *DynamicVariable) PreProcess(_ context.Context, _ *Global, function *Function) error { + if m.Name == "" { + suffix := function.VariableCounter + function.VariableCounter += 1 + m.Name = "_" + function.Name + "_" + strconv.Itoa(suffix) + } + return nil +} + +func (m *DynamicVariable) PostProcess(context.Context, *Global, *Function) error { + return nil +} + +func (m *DynamicVariable) GetValue() string { + if m.Name == "" { + panic("PreProcess not called on DynamicVariable") + } + return m.Name +} diff --git a/transpiler/types.go b/transpiler/types.go index da5598e..58803f1 100644 --- a/transpiler/types.go +++ b/transpiler/types.go @@ -3,48 +3,8 @@ package transpiler import ( "context" "fmt" - "go/ast" - "strconv" ) -type Global struct { - Functions []*Function - Constants map[string]bool -} - -type Function struct { - Name string - Declaration *ast.FuncDecl - Statements []MLOGStatement - ArgumentCount int - VariableCounter int -} - -type MLOGAble interface { - ToMLOG() [][]Resolvable - GetComment() string -} - -type Processable interface { - PostProcess(context.Context, *Global, *Function) error -} - -type WithPosition interface { - GetPosition() int - Size() int -} - -type MutablePosition interface { - SetPosition(int) int -} - -type MLOGStatement interface { - MLOGAble - WithPosition - MutablePosition - Processable -} - func MLOGToString(ctx context.Context, statements [][]Resolvable, statement MLOGAble, lineNumber int) string { result := "" for _, line := range statements { @@ -72,391 +32,3 @@ func MLOGToString(ctx context.Context, statements [][]Resolvable, statement MLOG } return result } - -type MLOG struct { - Statement [][]Resolvable - Position int - Comment string -} - -func (m *MLOG) ToMLOG() [][]Resolvable { - return m.Statement -} - -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(ctx, global, function); err != nil { - return err - } - } - } - return nil -} - -func (m *MLOG) GetPosition() int { - return m.Position -} - -func (m *MLOG) Size() int { - if len(m.Statement) == 0 { - panic("statement without instructions") - } - - return len(m.Statement) -} - -func (m *MLOG) SetPosition(position int) int { - m.Position = position - return 1 -} - -func (m *MLOG) GetComment() string { - return m.Comment -} - -type MLOGFunc struct { - Position int - Function Translator - Arguments []Resolvable - Variables []Resolvable - Unresolved []MLOGStatement -} - -func (m *MLOGFunc) ToMLOG() [][]Resolvable { - results := make([][]Resolvable, 0) - for _, statement := range m.Unresolved { - results = append(results, statement.ToMLOG()...) - } - return results -} - -func (m *MLOGFunc) GetPosition() int { - return m.Position -} - -func (m *MLOGFunc) Size() int { - return m.Function.Count(m.Arguments, m.Variables) -} - -func (m *MLOGFunc) SetPosition(position int) int { - m.Position = position - return m.Function.Count(m.Arguments, m.Variables) -} - -func (m *MLOGFunc) PostProcess(ctx context.Context, global *Global, function *Function) error { - if len(m.Variables) != m.Function.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(ctx, global, function); err != nil { - return err - } - } - - var err error - m.Unresolved, err = m.Function.Translate(m.Arguments, m.Variables) - if err != nil { - return err - } - - 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 *MLOGFunc) GetComment() string { - return "Call to native function" -} - -type JumpTarget interface { - Processable - WithPosition -} - -type MLOGJump struct { - MLOG - Condition []Resolvable - JumpTarget JumpTarget -} - -func (m *MLOGJump) ToMLOG() [][]Resolvable { - return [][]Resolvable{ - append([]Resolvable{ - &Value{Value: "jump"}, - &Value{Value: strconv.Itoa(m.JumpTarget.GetPosition())}, - }, m.Condition...), - } -} - -func (m *MLOGJump) Size() int { - return 1 -} - -func (m *MLOGJump) PostProcess(ctx context.Context, global *Global, function *Function) error { - for _, resolvable := range m.Condition { - if err := resolvable.PostProcess(ctx, global, function); err != nil { - return err - } - } - return m.JumpTarget.PostProcess(ctx, global, function) -} - -func (m *MLOGJump) GetComment() string { - if m.Comment == "" { - return "Jump to target" - } - return m.Comment -} - -type FunctionJumpTarget struct { - Statement WithPosition - FunctionName string -} - -func (m *FunctionJumpTarget) GetPosition() int { - return m.Statement.GetPosition() -} - -func (m *FunctionJumpTarget) Size() int { - return 1 -} - -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 Err(ctx, "unknown function: "+m.FunctionName) -} - -type Resolvable interface { - Processable - GetValue() string -} - -type Value struct { - Value string -} - -func (m *Value) GetValue() string { - return m.Value -} - -func (m *Value) PostProcess(context.Context, *Global, *Function) error { - return nil -} - -func (m *Value) String() string { - return m.Value -} - -type NormalVariable struct { - Name string - CalculatedName string -} - -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 - } else { - m.CalculatedName = "_" + function.Name + "_" + m.Name - } - } - return nil -} - -func (m *NormalVariable) GetValue() string { - if m.CalculatedName == "" { - panic("PostProcess not called on NormalVariable (" + m.Name + ")") - } - return m.CalculatedName -} - -type DynamicVariable struct { - Name string -} - -func (m *DynamicVariable) PostProcess(ctx context.Context, global *Global, function *Function) error { - if m.Name == "" { - suffix := function.VariableCounter - function.VariableCounter += 1 - m.Name = "_" + function.Name + "_" + strconv.Itoa(suffix) - } - return nil -} - -func (m *DynamicVariable) GetValue() string { - if m.Name == "" { - panic("PostProcess not called on DynamicVariable") - } - return m.Name -} - -type MLOGTrampoline struct { - MLOG - Variable string - Extra int -} - -func (m *MLOGTrampoline) ToMLOG() [][]Resolvable { - return [][]Resolvable{ - { - &Value{Value: "write"}, - &Value{Value: strconv.Itoa(m.Position + m.Extra)}, - &Value{Value: StackCellName}, - &Value{Value: m.Variable}, - }, - } -} - -func (m *MLOGTrampoline) GetComment() string { - return "Set Trampoline Address" -} - -type MLOGStackWriter struct { - MLOG - Action string - Extra int -} - -func (m *MLOGStackWriter) ToMLOG() [][]Resolvable { - return [][]Resolvable{ - { - &Value{Value: "op"}, - &Value{Value: m.Action}, - &Value{Value: stackVariable}, - &Value{Value: stackVariable}, - &Value{Value: strconv.Itoa(1 + m.Extra)}, - }, - } -} - -func (m *MLOGStackWriter) GetComment() string { - return "Update Stack Pointer" -} - -type StatementJumpTarget struct { - Statement WithPosition - After bool -} - -func (m *StatementJumpTarget) GetPosition() int { - if m.After { - return m.Statement.GetPosition() + m.Statement.Size() - } - return m.Statement.GetPosition() -} - -func (m *StatementJumpTarget) Size() int { - return 1 -} - -func (m *StatementJumpTarget) PostProcess(context.Context, *Global, *Function) error { - return nil -} - -type MLOGTrampolineBack struct { - MLOG -} - -func (m *MLOGTrampolineBack) ToMLOG() [][]Resolvable { - return [][]Resolvable{ - { - &Value{Value: "read"}, - &Value{Value: "@counter"}, - &Value{Value: StackCellName}, - &Value{Value: stackVariable}, - }, - } -} - -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" -}