melt/Luau/compatibility.go

168 lines
4.2 KiB
Go

package main
import (
luau "Luau/binding"
"context"
"fmt"
"os"
"strings"
c "github.com/TwiN/go-color"
sitter "github.com/smacker/go-tree-sitter"
)
func compatifyCode(sourceCode []byte, tree *sitter.Tree) string {
node := tree.RootNode()
if node.HasError() {
fmt.Println("Error parsing code")
return string(sourceCode)
}
// watch out for passing by reference
newSource := make([]byte, len(sourceCode))
copy(newSource, sourceCode)
type sub struct {
node *sitter.Node
toReplace string
}
var toSub []sub
var findExprs func(node sitter.Node)
findExprs = func(node sitter.Node) {
start := node.StartByte()
end := node.EndByte()
rand := randomString(int(end - start))
ntype := node.Type()
if ntype == "ifexp" || (ntype == "binexp" && node.Child(1).Type() == "//") || (ntype == "var_stmt" && node.Child(1).Type() == "//=") {
// replace unsupported statements/expressions with a random string
newSource = append(newSource[:start], append([]byte(rand), newSource[end:]...)...)
toSub = append(toSub, sub{&node, rand})
} else {
for i := range int(node.ChildCount()) {
findExprs(*(node.Child(i)))
}
}
}
findExprs(*node)
sourceString := string(newSource)
for _, s := range toSub {
var replacement string
node := s.node
ntype := node.Type()
switch ntype {
case "ifexp":
fmt.Println(c.InBlue("replacing"))
secondCond := node.Child(3).Type()
hasElseIf := node.Child(4).Type() == "elseif"
if !hasElseIf && map[string]bool{
"number": true,
"string": true,
"string_interp": true,
"true": true, // lelel
}[secondCond] {
// SPECIAL CASE: the second condition is guaranteed to be truthy
// this means it can be simplified to Lua's sorta-ternary operator, a and b or c
// doesn't simplify nested if expressions. GOOD ENOUGH
for i := range int(node.ChildCount()) {
child := node.Child(i)
fmt.Println(child.Type())
switch child.Type() {
case "then":
replacement += "and "
case "else":
replacement += "or "
case "elseif":
panic("elseif in special case")
case "if":
// nothing
default:
replacement += child.Content(sourceCode) + " "
}
}
} else {
replacement += "(function()"
for i := range int(node.ChildCount()) {
child := node.Child(i)
fmt.Println(child.Type())
switch child.Type() {
case "if", "elseif", "then":
replacement += child.Content(sourceCode) + " "
case "else":
replacement += "end "
default:
prev := node.Child(i - 1).Type()
if prev == "then" || prev == "else" {
replacement += "return "
}
replacement += child.Content(sourceCode) + " "
}
}
replacement += "end)()"
}
case "binexp":
// child 1 is the operator (//)
left := node.Child(0).Content(sourceCode)
right := node.Child(2).Content(sourceCode)
replacement = fmt.Sprintf("math.floor(%s/%s)", left, right)
case "var_stmt":
// child 1 is the operator (//=)
left := node.Child(0).Content(sourceCode)
right := node.Child(2).Content(sourceCode)
// todo: make it so if one of the exprs is a function it don't get evaluated twice
// nah jk im not doing that
replacement = fmt.Sprintf("%s=math.floor(%s/%s)", left, left, right)
default:
panic("unhandled node type " + ntype)
}
sourceString = strings.Replace(sourceString, s.toReplace, replacement, 1)
}
// replace all ending newlines with a single newline
sourceString = strings.Trim(sourceString, "\n")
return sourceString
}
func compatifyFile(filename string) {
binding := luau.GetLuau()
sourceCode, err := os.ReadFile(filename)
if err != nil {
fmt.Println(err)
return
}
code := string(sourceCode)
// keep parsing until no changes are made lmao
for {
parser := sitter.NewParser()
parser.SetLanguage(binding)
newSource := []byte(code)
tree, err := parser.ParseCtx(context.Background(), nil, newSource)
if err != nil {
fmt.Println(err)
return
}
compatible := compatifyCode(newSource, tree)
// replace all ending newlines with a single newline
compatible = strings.Trim(compatible, "\n")
if compatible == string(code) {
break
}
code = compatible
}
fmt.Println(code)
}