From 7f2e64688ac78c6a0937a155d4503eb2dd38faba Mon Sep 17 00:00:00 2001 From: dbaumgarten Date: Tue, 24 Aug 2021 22:30:42 +0200 Subject: [PATCH] Externalizing of constants #78 --- examples/nolol/array_test.yaml | 2 +- examples/nolol/functions.nolol | 2 +- examples/nolol/goto_test.yaml | 2 +- examples/nolol/var_renaming_test.yaml | 2 +- pkg/nolol/converter.go | 13 ++ pkg/nolol/converter_constants.go | 181 ++++++++++++++++++++++++++ pkg/nolol/converter_test.go | 9 +- 7 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 pkg/nolol/converter_constants.go diff --git a/examples/nolol/array_test.yaml b/examples/nolol/array_test.yaml index ffa4a29..23a9ad7 100644 --- a/examples/nolol/array_test.yaml +++ b/examples/nolol/array_test.yaml @@ -5,4 +5,4 @@ cases: - name: WriteAndRead outputs: sum: "2,4,6,8," - o: "table> is at line: 2" + o: "table> is at line: 3" diff --git a/examples/nolol/functions.nolol b/examples/nolol/functions.nolol index 3e95050..68d8f13 100644 --- a/examples/nolol/functions.nolol +++ b/examples/nolol/functions.nolol @@ -1,4 +1,4 @@ -:timeok=time() +:timeok=time()==2 :absok=abs(-5)==ABS(5) if abs(-2)==abs(2) then :absinfiok=1 diff --git a/examples/nolol/goto_test.yaml b/examples/nolol/goto_test.yaml index 1cddade..5edc2e3 100644 --- a/examples/nolol/goto_test.yaml +++ b/examples/nolol/goto_test.yaml @@ -4,4 +4,4 @@ cases: - name: TestOutputstring outputs: out: "abcdef" - text: "d is at line: 4" + text: "d is at line: 5" diff --git a/examples/nolol/var_renaming_test.yaml b/examples/nolol/var_renaming_test.yaml index 64cdecd..5c6b969 100644 --- a/examples/nolol/var_renaming_test.yaml +++ b/examples/nolol/var_renaming_test.yaml @@ -3,4 +3,4 @@ scripts: cases: - name: TestRenaming outputs: - out: "hallo welt123 2" + out: "hallo welt123 4" diff --git a/pkg/nolol/converter.go b/pkg/nolol/converter.go index 9bfc29b..550c0bb 100644 --- a/pkg/nolol/converter.go +++ b/pkg/nolol/converter.go @@ -228,6 +228,19 @@ func (c *Converter) ProcessNodes() ConverterLines { c.varnameOptimizer.OptimizeVarName(reservedTimeVariable) } + // optimize static expressions before moving constants around in the next step + err = c.sexpOptimizer.Optimize(c.prog) + if err != nil { + c.err = err + return c + } + + err = c.globalizeConstants(c.prog) + if err != nil { + c.err = err + return c + } + // find all user-defined line-labels err = c.findLineLabels(c.prog, false) if err != nil { diff --git a/pkg/nolol/converter_constants.go b/pkg/nolol/converter_constants.go new file mode 100644 index 0000000..fb4fcb9 --- /dev/null +++ b/pkg/nolol/converter_constants.go @@ -0,0 +1,181 @@ +package nolol + +import ( + "fmt" + "sort" + "strings" + + "github.com/dbaumgarten/yodk/pkg/nolol/nast" + "github.com/dbaumgarten/yodk/pkg/parser/ast" +) + +type constant struct { + Expression ast.Expression + Replacement string +} + +func (c *Converter) globalizeConstants(prog *nast.Program) error { + + counter := 0 + templ := "_const%d" + + numberConstants := make(map[string]constant) + stringConstants := make(map[string]constant) + + store := func(value string, exp ast.Expression, set map[string]constant) *ast.Dereference { + old, exists := set[value] + if exists { + return &ast.Dereference{ + Position: exp.Start(), + Variable: old.Replacement, + } + } + c := constant{ + Expression: exp, + Replacement: fmt.Sprintf(templ, counter), + } + set[value] = c + counter++ + return &ast.Dereference{ + Position: exp.Start(), + Variable: c.Replacement, + } + } + + findConstants := func(node ast.Node, visitType int) error { + if visitType == ast.PostVisit || visitType == ast.SingleVisit { + numConst, isNumber := node.(*ast.NumberConstant) + stringConst, isString := node.(*ast.StringConstant) + + if isNumber && len(numConst.Value) > 1 { + repl := store(numConst.Value, numConst, numberConstants) + return ast.NewNodeReplacementSkip(repl) + } else if isString { + repl := store(stringConst.Value, stringConst, stringConstants) + return ast.NewNodeReplacementSkip(repl) + } + + } + return nil + } + + err := prog.Accept(ast.VisitorFunc(findConstants)) + if err != nil { + return err + } + + constantlist := make([]constant, 0, len(stringConstants)+len(numberConstants)) + for _, con := range stringConstants { + constantlist = append(constantlist, con) + } + for _, con := range numberConstants { + constantlist = append(constantlist, con) + } + + sortConstantList(constantlist) + + // find variables that are assigned ONLY ONCE and with an globalized constant + // These assignments are removed and all future dereferences are replaced with the globalized constant + constantAliases := make(map[string]string) + nonConstantAliases := make(map[string]bool) + + findConstantAliases := func(node ast.Node, visitType int) error { + if ass, is := node.(*ast.Assignment); is && visitType == ast.PreVisit { + + // Ignore it if there is more than one assignment + if _, alreadyExists := constantAliases[strings.ToLower(ass.Variable)]; alreadyExists { + nonConstantAliases[strings.ToLower(ass.Variable)] = true + } + + if val, is := ass.Value.(*ast.Dereference); is { + // only when the value is a variable starting with __const and the variable is not a global + if strings.HasPrefix(val.Variable, "_const") && !strings.HasPrefix(ass.Variable, ":") { + constantAliases[strings.ToLower(ass.Variable)] = val.Variable + } + } + } + + // ignore variables that are dereferenced with a modifying operator + if deref, is := node.(*ast.Dereference); is && visitType == ast.SingleVisit { + if deref.Operator != "" { + nonConstantAliases[strings.ToLower(deref.Variable)] = true + } + } + return nil + } + + err = prog.Accept(ast.VisitorFunc(findConstantAliases)) + if err != nil { + return err + } + + // remove the variables that are determined as non-candidates from our list + for k := range nonConstantAliases { + delete(constantAliases, k) + } + + replaceConstantAliases := func(node ast.Node, visitType int) error { + if ass, is := node.(*ast.Assignment); is && visitType == ast.PreVisit { + if _, exists := constantAliases[strings.ToLower(ass.Variable)]; exists { + return ast.NewNodeReplacement() + } + } + if deref, is := node.(*ast.Dereference); is && visitType == ast.SingleVisit { + alias, exists := constantAliases[strings.ToLower(deref.Variable)] + if exists { + deref.Variable = alias + } + } + return nil + } + + err = prog.Accept(ast.VisitorFunc(replaceConstantAliases)) + if err != nil { + return err + } + + // Add the globalized constants and wrap the original code in a while loop + + lines := make([]nast.Element, 0, len(constantlist)+1) + for _, con := range constantlist { + lines = append(lines, &nast.StatementLine{ + Line: ast.Line{ + Position: ast.UnknownPosition, + Statements: []ast.Statement{ + &ast.Assignment{ + Position: ast.UnknownPosition, + Variable: con.Replacement, + Operator: "=", + Value: con.Expression, + }, + }, + }, + }) + } + + loop := &nast.WhileLoop{ + Position: ast.UnknownPosition, + Condition: &ast.NumberConstant{ + Value: "1", + }, + Block: &nast.Block{ + Elements: make([]nast.NestableElement, 0, len(prog.Elements)), + }, + } + for _, el := range prog.Elements { + loop.Block.Elements = append(loop.Block.Elements, el.(nast.NestableElement)) + } + + lines = append(lines, loop) + + prog.Elements = lines + return nil +} + +func sortConstantList(li []constant) { + sort.Slice(li, func(i, j int) bool { + one := li[i] + two := li[j] + return one.Replacement > two.Replacement + }) +} diff --git a/pkg/nolol/converter_test.go b/pkg/nolol/converter_test.go index cb70714..8b953a6 100644 --- a/pkg/nolol/converter_test.go +++ b/pkg/nolol/converter_test.go @@ -44,8 +44,8 @@ y="hey" $ $ foo="bar" -$ what="ever" -$ z = 0 $ +$ :what="ever" +$ :z = 0 $ x = 99 ` @@ -117,6 +117,9 @@ func TestLineHandling(t *testing.T) { lines := len(prog.Lines) if lines != 8 { - t.Fatal("Wrong amount of lines after merging. Expected 8, but got: ", lines) + pri := &parser.Printer{} + txt, _ := pri.Print(prog) + fmt.Println(txt) + t.Fatal("Wrong amount of lines after merging. Expected 9, but got: ", lines) } }