Add compatibility transformer for turning some Luau syntax into Lua
This commit is contained in:
parent
97d14d4a23
commit
f7c6899f2d
|
|
@ -3,4 +3,6 @@
|
|||
|
||||
# Roblox Studio lock files
|
||||
/*.rbxlx.lock
|
||||
/*.rbxl.lock
|
||||
/*.rbxl.lock
|
||||
|
||||
/Sync/Server/tools
|
||||
|
|
|
|||
164
Luau/main.go
164
Luau/main.go
|
|
@ -4,6 +4,7 @@ import (
|
|||
luau "Luau/binding"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
|
|
@ -11,6 +12,15 @@ import (
|
|||
sitter "github.com/smacker/go-tree-sitter"
|
||||
)
|
||||
|
||||
func randomString(length int) string {
|
||||
// generate random unicode string
|
||||
var str string
|
||||
for i := 0; i < length; i++ {
|
||||
str += string(rune(rand.Intn(0x7E-0x21) + 0x21))
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func isInside(ntype string, node *sitter.Node) bool {
|
||||
for node != nil {
|
||||
if node.Type() == ntype {
|
||||
|
|
@ -23,7 +33,6 @@ func isInside(ntype string, node *sitter.Node) bool {
|
|||
|
||||
func formatCode(sourceCode []byte, tree *sitter.Tree) string {
|
||||
node := tree.RootNode()
|
||||
|
||||
if node.HasError() {
|
||||
fmt.Println("Error parsing code")
|
||||
return string(sourceCode)
|
||||
|
|
@ -68,7 +77,6 @@ func formatCode(sourceCode []byte, tree *sitter.Tree) string {
|
|||
writeIndent()
|
||||
formatted.WriteString("local ")
|
||||
case "name":
|
||||
|
||||
if parent.Parent().Type() == "call_stmt" && !isInside("local_var_stmt", &node) {
|
||||
if len(insideLocal) > 1 && insideLocal[len(insideLocal)-2] {
|
||||
formatted.WriteString("\n")
|
||||
|
|
@ -232,9 +240,127 @@ func formatCode(sourceCode []byte, tree *sitter.Tree) string {
|
|||
return formatted.String()
|
||||
}
|
||||
|
||||
func compatibility(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)
|
||||
}
|
||||
|
||||
return sourceString
|
||||
}
|
||||
|
||||
func main() {
|
||||
parser := sitter.NewParser()
|
||||
parser.SetLanguage(luau.GetLuau())
|
||||
binding := luau.GetLuau()
|
||||
|
||||
filename := "test.luau"
|
||||
|
||||
|
|
@ -244,17 +370,27 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
tree, _ := parser.ParseCtx(context.Background(), nil, sourceCode)
|
||||
formatted := formatCode(sourceCode, tree)
|
||||
code := string(sourceCode)
|
||||
|
||||
// replace all ending newlines with a single newline
|
||||
formatted = strings.Trim(formatted, "\n") + "\n"
|
||||
// keep parsing until no changes are made lmao
|
||||
for {
|
||||
parser := sitter.NewParser()
|
||||
parser.SetLanguage(binding)
|
||||
|
||||
// write back to file
|
||||
err = os.WriteFile(filename, []byte(formatted), 0o644)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
newSource := []byte(code)
|
||||
tree, err := parser.ParseCtx(context.Background(), nil, newSource)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
compatible := compatibility(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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue