init + feat: initial darklua backends impl

This commit is contained in:
Erica Marigold 2023-09-26 19:14:58 +05:30
commit e14389c538
No known key found for this signature in database
GPG key ID: 7843994FD1386E35
11 changed files with 526 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# Wax related dirs
.wax
.wax-tmp
# Compiled outputs
terracotta.luau

50
.lune/make.luau Normal file
View file

@ -0,0 +1,50 @@
local spawn = require("@lune/process").spawn
local stdio = require("@lune/stdio")
local fs = require("@lune/fs")
local codegen = require("../darklua/codegen")
local WAX_PREFIX_FORMAT = function(type: "error" | "info", scope: string?)
return stdio.color(if type == "error" then "red" else "green") .. (scope or "[wax]") .. stdio.color("reset")
end
print(`{WAX_PREFIX_FORMAT("info", "[codegen]")} Generating darklua config file at darklua.config.json`)
fs.writeFile(
".darklua.json5",
codegen({
generator = "dense",
rules = {
"remove_spaces",
"rename_variables",
},
})
)
print()
print("------------------------------------------------------------------")
codegen({
rules = {
"config",
},
})
local child = spawn(
"lune",
{ "wax", "bundle", "input=default.project.json", "verbose=true", "minify=true", "output=terracotta.luau" },
{
stdio = "inherit",
}
)
print("------------------------------------------------------------------")
if not child.ok then
print()
print(`{WAX_PREFIX_FORMAT("error")} Exited with code {child.code}.`)
else
print()
print(`{WAX_PREFIX_FORMAT("info")} Successfully built terracotta.luau!`)
end
fs.removeFile(".darklua.json5")

33
.lune/wax.luau Normal file
View file

@ -0,0 +1,33 @@
--[[
Wax - A Fast Runtime Lua 5.1x+/Luau Project Bundler, Using Roblox Models and Module-Require Semantics
MIT License | Copyright (c) 2023 Latte Softworks <https://latte.to>
]]
-- You set the following string to "latest" (case insensitive), or any version tag
-- on Wax's releases page (e.g. "0.1.1")
local WaxVersion = "latest"
local WaxPath = ".wax/wax-${version}.luau"
-------------------------------------------------------------------------------
local net = require("@lune/net")
local luau = require("@lune/luau")
local fs = require("@lune/fs")
local FilePath
if not fs.isFile(WaxPath) then
if not fs.isDir(".wax") then
fs.writeDir(".wax")
end
FilePath = WaxPath:gsub("${version}", WaxVersion)
local FileLink = if string.lower(WaxVersion) == "latest"
then "https://github.com/latte-soft/wax/releases/latest/download/wax.luau"
else `https://github.com/latte-soft/wax/releases/download/{WaxVersion}/wax.luau`
fs.writeFile(FilePath, net.request(FileLink).body)
end
luau.load(fs.readFile(FilePath))()

7
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,7 @@
{
"luau-lsp.require.mode": "relativeToFile",
"luau-lsp.require.directoryAliases": {
"@lune/": "~/.lune/.typedefs/0.7.7/"
},
"editor.formatOnSave": true
}

64
darklua/bundler.luau Normal file
View file

@ -0,0 +1,64 @@
local Fs = require("@lune/fs")
local Darklua = require("cmd")
local FsUtils = require("../utils/fs")
local Codegen = require("codegen")
local Bundler = {}
type BundlerExtended =
typeof(Bundler.Prototype)
& { darkluaPath: string?, darklua: Darklua.Darklua?, config: { contents: string, path: string? } }
Bundler.Prototype = {}
Bundler.Interface = {}
Bundler.Type = "Bundler"
function Bundler.Prototype.Bundle(
self: BundlerExtended,
sourceKind: "code" | "path",
sourceInner: string
): { error: string?, bundled: string? }
self.darklua = Darklua.new(self.darkluaPath)
if self.darklua:IsOk() then
return self.darklua:Process("code", sourceInner, self.config.path or (function()
local _, path = FsUtils.MakeTemp()
Fs.writeFile(
path,
Codegen({
generator = self.config.contents.generator,
rules = self.config.contents.rules,
})
)
return path
end)())
end
return {
error = "constructed darklua instance was not OK",
}
end
function Bundler.Prototype.ToString(): string
return string.format("%s<%s>", Bundler.Type, "{}")
end
function Bundler.Interface.new(darkluaPath: string?, opts: Codegen.Options)
return setmetatable({
darkluaPath = darkluaPath,
darklua = nil,
config = {
contents = opts,
path = nil,
},
}, {
__index = Bundler.Prototype,
__type = Bundler.Type,
__tostring = function()
return Bundler.Prototype.ToString()
end,
})
end
return Bundler.Interface

203
darklua/cmd.luau Normal file
View file

@ -0,0 +1,203 @@
--!strict
local Process = require("@lune/process")
local Fs = require("@lune/fs")
local FsUtils = require("../utils/fs")
local Darklua = {}
type DarkluaExtended = typeof(Darklua.Prototype) & { darkluaPath: string }
-- TODO: Implement wrappers around fs & process APIs that do not panic
-- TODO: binbows support :3
-- Utility function to look for a binary in the PATH and return its absolute path
local function ProcessWhich(binName: string): { path: string?, warnings: { string } | {} }
local path: string = Process.env.PATH
local binPath: string? = nil
local warnings: { string } = {}
-- If the PATH is set, and is a string, we loop divide the path into dirs
if path and typeof(path) == "string" then
for _, dir in path:split(":") do
-- [1] => whether xpcall was okful
-- [2] => collection of files in the dir
local dirFiles: { any } = table.pack(xpcall(function()
-- We try to read the dir
return Fs.readDir(dir)
end, function()
-- If it fails, we push to warnings, and return an empty table
table.insert(warnings, string.format("%s - NOT_FOUND", dir))
return {}
end))[2] :: { any }
local binIdx = table.find(dirFiles, binName)
-- Find the position of the binName in our directory, we strip the
-- trailing slash, and set the dirPath in the upper scope.
if binIdx then
local dirPath: string = (function()
if string.sub(dir, -1) == "/" then
return dir:sub(1, -1)
else
return dir
end
end)()
binPath = dirPath .. "/" .. dirFiles[binIdx]
end
end
end
return {
path = binPath,
warnings = warnings,
}
end
Darklua.Type = "Darklua"
Darklua.Interface = {}
Darklua.Prototype = {}
function Darklua.Prototype.ToString(self: DarkluaExtended): string
return string.format("%s<%s>", Darklua.Type, "Global")
end
function Darklua.Prototype.IsOk(self: DarkluaExtended): boolean
if self.darkluaPath and Fs.isFile(self.darkluaPath) then
local isExecutable = Process.spawn(self.darkluaPath, { "--help" }, { stdio = "default" }).ok
return isExecutable
end
return false
end
function Darklua.Prototype.Process(
self: DarkluaExtended,
sourceKind: "code" | "path",
source: string,
configPath: string?
): { error: string?, processed: string? }
local sourcePath: string
if sourceKind == "code" then
local ok, tmpFilePath = FsUtils.MakeTemp()
if not ok then
return {
error = "failed to create temporary file for darklua minification",
}
end
Fs.writeFile(tmpFilePath, source)
sourcePath = tmpFilePath
elseif sourceKind == "path" then
sourcePath = source
else
return {
error = "invalid sourceKind provided",
}
end
local ok, outFilePath = FsUtils.MakeTemp()
if not ok then
return {
error = "failed to create temporary file for darklua minification",
}
end
local args = { "--config" }
if configPath and configPath ~= "" then
table.insert(args, configPath)
else
args = {}
end
local darkluaChild = Process.spawn(self.darkluaPath, { "process", sourcePath, outFilePath, table.unpack(args) })
if not darkluaChild.ok then
return {
error = "darkluaChild spawning failed, error:\n" .. darkluaChild.stderr,
}
end
local processedContents = Fs.readFile(outFilePath)
return {
processed = processedContents,
}
end
function Darklua.Prototype.Minify(
self: DarkluaExtended,
sourceKind: "code" | "path",
source: string
): { error: string?, minified: string? }
local sourcePath: string
if sourceKind == "code" then
local ok, tmpFilePath = FsUtils.MakeTemp()
if not ok then
return {
error = "failed to create temporary file for darklua minification",
}
end
Fs.writeFile(tmpFilePath, source)
sourcePath = tmpFilePath
elseif sourceKind == "path" then
sourcePath = source
else
return {
error = "invalid sourceKind provided",
}
end
local ok, outFilePath = FsUtils.MakeTemp()
if not ok then
return {
error = "failed to create temporary file for darklua minification",
}
end
local darkluaChild = Process.spawn(self.darkluaPath, { "minify", sourcePath, outFilePath })
if not darkluaChild.ok then
return {
error = "darkluaChild spawning failed, error:\n" .. darkluaChild.stderr,
}
end
local minifiedContents = Fs.readFile(outFilePath)
return {
minified = minifiedContents,
}
end
function Darklua.Interface.new(darkluaPath: string?)
local inst = setmetatable({
darkluaPath = darkluaPath or ProcessWhich("darklua").path,
}, {
__index = Darklua.Prototype,
__type = Darklua.Type,
__tostring = function(self: Darklua)
return self:ToString()
end,
})
return inst :: typeof(inst)
end
-- Here, we provide a path as to stop the ProcessWhich, which adds aditional
-- processing we don't need.
export type Darklua = typeof(Darklua.Interface.new("NON_EXISTENT_PATH"))
return Darklua.Interface

76
darklua/codegen.luau Normal file
View file

@ -0,0 +1,76 @@
local Utils = require("../utils/misc")
local JSONEncode = require("@lune/net").jsonEncode
export type Generators = "retain_lines" | "dense" | "readable"
export type Rules =
"compute_expression"
| "convert_index_to_field"
| "convert_local_function_to_assign"
| "convert_require"
| "filter_after_early_return"
| "group_local_assignment"
| "inject_global_value"
| "remove_comments"
| "remove_compound_assignment"
| "remove_empty_do"
| "remove_function_call_parens"
| "remove_method_definition"
| "remove_nil_declaration"
| "remove_spaces"
| "remove_unused_if_branch"
| "remove_unused_while"
| "rename_variables"
export type Options = {
generator: Generators?,
excludes: { string }?,
requireMode: "path"?,
moduleFolderName: string?,
sources: { [string]: string }?,
rules: {
Rules | {
rule: Rules,
identifier: string,
value: any,
} | {
rule: Rules,
[any]: any,
}
}?,
}
return function(opts: Options): string
local generator: Generators = opts.generator or "retain_lines"
local encodedExcludes = JSONEncode(opts.excludes)
local excludes = (encodedExcludes == "null" and "[]") or encodedExcludes
local requireMode = opts.requireMode or "path"
local moduleFolderName = opts.moduleFolderName or "init"
local sources = JSONEncode(opts.sources or { ["@terracotta"] = "./.terracotta" })
local encodedRules = JSONEncode(opts.rules)
local rules = (encodedRules == "null" and "[]") or encodedRules
local template = [[{
generator: "${generator}",
bundle: {
modules_identifier: "__TERRACOTTA__",
excludes: ${excludes},
require_mode: {
name: "${requireMode}",
module_folder_name: "${moduleFolderName}",
sources: ${sources}
}
},
rules: ${rules}
}]]
return Utils.Format(template, {
generator = generator,
excludes = excludes,
requireMode = requireMode,
moduleFolderName = moduleFolderName,
sources = sources,
rules = rules,
})
end

43
darklua/minifier.luau Normal file
View file

@ -0,0 +1,43 @@
local Darklua = require("cmd")
local Minifier = {}
type MinfierExtended = typeof(Minifier.Prototype) & { darkluaPath: string?, darklua: Darklua.Darklua? }
Minifier.Prototype = {}
Minifier.Interface = {}
Minifier.Type = "Minifier"
function Minifier.Prototype.Minify(
self: MinfierExtended,
sourceKind: "code" | "path",
sourceInner: string
): { error: string?, minified: string? }
self.darklua = Darklua.new(self.darkluaPath)
if self.darklua:IsOk() then
return self.darklua:Minify("code", sourceInner)
end
return {
error = "constructed darklua instance was not OK",
}
end
function Minifier.Prototype.ToString(): string
return string.format("%s<%s>", Minifier.Type, "{}")
end
function Minifier.Interface.new(darkluaPath: string?)
return setmetatable({
darkluaPath = darkluaPath,
darklua = nil,
}, {
__index = Minifier.Prototype,
__type = Minifier.Type,
__tostring = function()
return Minifier.Prototype.ToString()
end,
})
end
return Minifier.Interface

12
default.project.json Normal file
View file

@ -0,0 +1,12 @@
{
"name": "terracotta",
"tree": {
"$className": "Folder",
"terracotta": {
"$path": "src/"
},
"darklua": {
"$path": "darklua/"
}
}
}

12
utils/fs.luau Normal file
View file

@ -0,0 +1,12 @@
local Process = require("@lune/process")
local Utils = {}
-- Utility function to create a temporary directory quickly
function Utils.MakeTemp(): (boolean, string)
local filePathChild = Process.spawn("mktemp")
return filePathChild.ok, (filePathChild.ok and filePathChild.stdout:gsub("%s", "")) or ""
end
return Utils

20
utils/misc.luau Normal file
View file

@ -0,0 +1,20 @@
local _Net = require("@lune/net")
local JSONEncode, JSONDecode = _Net.jsonEncode, _Net.jsonDecode
local MiscUtils = {}
function MiscUtils.Format(formatStr: string, with: {[string]: string})
return table.pack(string.gsub(formatStr, "\$\{(%a+)\}", function(s)
return with[s] or ""
end))[1]
end
function MiscUtils.PrettifyJson(json: string | {[any]: any}): string
if typeof(json) == "table" then
return JSONEncode(json, true)
else
return JSONEncode(JSONDecode(json), true)
end
end
return MiscUtils