Skip to content

Commit

Permalink
Externalizing of constants #78
Browse files Browse the repository at this point in the history
  • Loading branch information
dbaumgarten committed Dec 27, 2021
1 parent e390c70 commit 7f2e646
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 7 deletions.
2 changes: 1 addition & 1 deletion examples/nolol/array_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion examples/nolol/functions.nolol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
:timeok=time()
:timeok=time()==2
:absok=abs(-5)==ABS(5)
if abs(-2)==abs(2) then
:absinfiok=1
Expand Down
2 changes: 1 addition & 1 deletion examples/nolol/goto_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ cases:
- name: TestOutputstring
outputs:
out: "abcdef"
text: "d is at line: 4"
text: "d is at line: 5"
2 changes: 1 addition & 1 deletion examples/nolol/var_renaming_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ scripts:
cases:
- name: TestRenaming
outputs:
out: "hallo welt123 2"
out: "hallo welt123 4"
13 changes: 13 additions & 0 deletions pkg/nolol/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
181 changes: 181 additions & 0 deletions pkg/nolol/converter_constants.go
Original file line number Diff line number Diff line change
@@ -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
})
}
9 changes: 6 additions & 3 deletions pkg/nolol/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ y="hey"
$
$
foo="bar"
$ what="ever"
$ z = 0 $
$ :what="ever"
$ :z = 0 $
x = 99
`

Expand Down Expand Up @@ -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)
}
}

0 comments on commit 7f2e646

Please sign in to comment.