WIP: awesomerc.tl should work #85

Draft
Aire-One wants to merge 40 commits from feat/#58 into master
8 changed files with 293 additions and 76 deletions
Showing only changes of commit 3b3ba3476f - Show all commits

View File

@ -211,4 +211,60 @@ describe("Teal type definition Printer", function()
return Timer
]]))
it("should print a module with descendants", gen(
{
children = {
{
children = {},
name = "Signal",
token = "enum",
},
{
parameters = {},
return_types = {},
name = "draw_to_cairo_context",
token = "function",
},
{
children = {
{
children = {},
name = "Signal",
token = "enum",
},
{
parameters = {},
return_types = {},
name = "imagebox",
metamethod = "__call",
token = "metamethod",
}
},
name = "Imagebox",
module_path = "wibox.widget.imagebox",
dependencies = {},
global = false,
token = "module",
}
},
name = "Widget",
module_path = "wibox.widget",
dependencies = {},
global = false,
token = "module",
},
[[
-- This file was auto-generated.
local type Imagebox = require("wibox.widget.imagebox")
local record Widget
enum Signal
end
draw_to_cairo_context: function()
imagebox: Imagebox
end
return Widget
]]))
end)

View File

@ -14,7 +14,7 @@ local basic_nodes <total>: { Node.Token : function(name: string, ...: any): Node
record = function(name: string, global: boolean): Node
return {
token = "record",
global = global,
global = global,
name = name,
children = {},
}
@ -70,6 +70,40 @@ local function create_node(token: Node.Token, name: string, ...: any): Node
return node
end
-- TODO : be less permissive and allow to merge only module ?
local function merge_nodes(node: Node, other: Node): Node
if node.token ~= other.token then
error("Cannot merge nodes of different types")
end
if node.name ~= other.name then
error("Cannot merge nodes of different names")
end
if node.module_path ~= other.module_path then
error("Cannot merge nodes of different module paths")
end
-- Basic fields should be overwritten by the new values
-- Teal wants us to use enums value to index a record, but it fails to understand my enum with
-- any other key from Node, so I'm forcing it to cooperate for now
local enum NodeField
"types"
end
for _, field in ipairs({"global", "types", "parameters", "return_types", "metamethod"}) do
local f = field as NodeField
node[f] = other[f]
end
-- Children should be merged
for _, child in ipairs(other.children) do
table.insert(node.children, child)
end
-- Dependencies should be merged
for name, path in pairs(other.dependencies) do
node.dependencies[name] = path
end
end
local function iter_children(node: Node): function(): integer, Node
if node.children == nil then
return function(): integer, Node end
@ -77,6 +111,7 @@ local function iter_children(node: Node): function(): integer, Node
return ipairs(node.children)
end
-- TODO : deprecate in favor of the iterator definition ?
local function in_order_visitor(node: Node, visitor: function(Node))
for _, child in iter_children(node) do
in_order_visitor(child, visitor)
@ -84,8 +119,28 @@ local function in_order_visitor(node: Node, visitor: function(Node))
visitor(node)
end
local function iter_in_order(node: Node): function(): Node
local stack: { Node } = {}
local function traverse(n: Node)
for _, child in iter_children(n) do
traverse(child)
end
table.insert(stack, n)
end
traverse(node)
local i = 0
return function(): Node
i = i + 1
return stack[i]
end
end
return {
create_node = create_node,
merge_nodes = merge_nodes,
iter_children = iter_children,
in_order_visitor = in_order_visitor,
iter_in_order = iter_in_order,
}

View File

@ -1,3 +1,4 @@
local ast = require("awesomewmdtl.ast")
local type Dag = require("awesomewmdtl.types.Dag")
local type Node = require("awesomewmdtl.types.Node")
local utils <const> = require("awesomewmdtl.utils")
@ -10,26 +11,98 @@ local function new(): Dag
return dag
end
local function push_module(dag: Dag, module_path: string, root_node: Node)
dag.modules[module_path] = root_node
local function push_module(dag: Dag, module_path: string, node: Node)
local parent: Node = { children = dag.modules }
local current_path = ""
for breadcrumb in module_path:gmatch("([^%.]+)") do
current_path = current_path == "" and breadcrumb or current_path .. "." .. breadcrumb
if current_path == node.module_path then
local found = utils.find(parent.children, function(n: Node): boolean
return n.module_path == node.module_path
end)
if found then
ast.merge_nodes(found, node)
else
table.insert(parent.children, node)
end
return
end
local current: Node = nil
for _, n in ipairs(parent.children) do
if n.module_path == current_path then
current = n
break
end
end
if current == nil then
current = {
children = {},
name = utils.capitalize(breadcrumb),
module_path = current_path,
dependencies = {},
token = "module",
}
push_module(dag, current_path, current)
end
parent = current
end
end
local function push_global_nodes(dag: Dag, nodes: { Node })
utils.spread(dag.global_nodes, nodes)
end
local function iter_modules(dag: Dag): (function(): string, Node)
return pairs(dag.modules)
local function iter_modules(dag: Dag): (function(): Node)
local stack: { Node } = {}
for _, root in ipairs(dag.modules) do
for node in ast.iter_in_order(root) do
if node.token == "module" then
table.insert(stack, node)
end
end
end
local i = 0
return function(): Node
i = i + 1
return stack[i]
end
end
local function iter_global_nodes(dag: Dag): (function(): integer, Node)
return pairs(dag.global_nodes)
end
local function find_module(dag: Dag, module_path: string): Node
local current_path = ""
for breadcrumb in module_path:gmatch("([^%.]+)") do
current_path = current_path == "" and breadcrumb or current_path .. "." .. breadcrumb
local current: Node = nil
if current_path == module_path then
return current
end
for _, n in ipairs(dag.modules) do
if n.module_path == current_path then
current = n
break
end
end
if current == nil then
return nil
end
end
end
return {
new = new,
push_module = push_module,
push_global_nodes = push_global_nodes,
iter_modules = iter_modules,
iter_global_nodes = iter_global_nodes,
find_module = find_module,
}

View File

@ -9,14 +9,12 @@ local filesystem <const> = require("awesomewmdtl.filesystem")
local printer <const> = require("awesomewmdtl.printer")
local List <const> = require("pl.List")
local logger <const> = require("awesomewmdtl.logger")
local Map <const> = require("pl.Map")
local Module_Info <const> = require("awesomewmdtl.entity.Module_Info")
local module_dependencies <const> = require("awesomewmdtl.visitors.module_dependencies")
local type Node = require("awesomewmdtl.types.Node")
local node_fixer <const> = require("awesomewmdtl.visitors.node_fixer")
local property <const> = require("awesomewmdtl.property")
local scraper <const> = require("awesomewmdtl.scraper")
local stringx <const> = require("pl.stringx")
local type_mapping <const> = require("awesomewmdtl.visitors.type_mapping")
local utils <const> = require("awesomewmdtl.utils")
@ -46,53 +44,6 @@ local function module_lists(
return all_module_infos, module_infos, global_module_infos
end
-- The module's children list produced can contain duplicates.
-- We ignore them for now because they are dismissed when building a Map for the printer.
local function modules_tree(modules: List<Module_Info.Module_Info>): Map<string, List<string>>
local tree: Map<string, List<string>> = Map()
for module in modules:iter() do
local parent = module.name:gmatch("(.*)%.(.*)$")()
if parent then
local ancestors = stringx.split(parent, ".")
for i = 1, #ancestors - 1 do
local ancestor = ancestors:slice(1, i):join(".")
if not tree:get(ancestor) then
tree:set(ancestor, List())
end
if not tree:get(ancestor):contains(parent) then
tree:get(ancestor):append(parent)
end
end
local parent_node = tree:get(parent)
if not parent_node then
tree:set(parent, List())
parent_node = tree:get(parent)
end
parent_node:append(module.name)
end
end
return tree
end
--- TODO : rewrite this to use the DAG
local function do_module_init_definition(
module_infos: List<Module_Info.Module_Info>
)
local tree = modules_tree(module_infos)
for module, children in tree:iter() do
-- TODO : this map should be coupled with the all_module_infos list
local requires: Map<string, string> = Map()
for child in children:iter() do
local name = child:gmatch(".*%.(.*)$")()
requires:set(name, child)
end
filesystem.file_writer.write(
printer.module_init_definition.generate_teal(requires),
property.out_directory .. "/" .. stringx.split(module, "."):slice(1, -1):join("/") .. "/init.d.tl"
)
end
end
--- TODO : rewrite the module_info thingy
local all_module_infos, module_infos, global_module_infos = module_lists(
property.base_url .. property.index_uri,
@ -130,7 +81,7 @@ for module in module_infos:iter() do
end
-- Run the visitors
for _,root in dag.iter_modules(module_dag) do
for root in dag.iter_modules(module_dag) do
ast.in_order_visitor(root, function(node: Node)
node_fixer.visit(node, root)
end)
@ -172,17 +123,17 @@ end
-- Write the DAG to a file
-- local inspect = require("inspect")
-- filesystem.file_writer.write(
-- inspect(module_dag, { newline = "\n", indent = " ", depth = 2 }),
-- inspect(module_dag, { newline = "\n", indent = " " }),
-- "generated_dag.lua"
-- )
log:info("Preprocessing finished")
-- Write modules types definitions to files
for module_path, root in dag.iter_modules(module_dag) do
for root in dag.iter_modules(module_dag) do
filesystem.file_writer.write(
printer.teal_type_definition.printer(root),
property.out_directory .. "/" .. module_path:gsub("%.", "/") .. ".d.tl"
property.out_directory .. "/" .. root.module_path:gsub("%.", "/") .. ".d.tl"
)
end
@ -193,5 +144,4 @@ filesystem.file_writer.write(
property.out_directory .. "/global_env_def.d.tl"
)
do_module_init_definition(module_infos)
log:info("Module init files generated")

View File

@ -6,6 +6,25 @@ local utils = require("awesomewmdtl.utils")
local log = logger.log("scraper")
local GEN_TEXT <const> = "-- This file was auto-generated.\n"
local function get_package_descendants(node: Node): { Node }
return utils.filter(
node.children,
function(child: Node): boolean
return child.token == "module"
end
)
end
local function nodes_to_string_map(nodes: { Node }): { string : string }
local map <const>: { string : string } = {}
for _, node in ipairs(nodes) do
map[node.name] = node.module_path
end
return map
end
local function render_types(types: { string }, separator: string, with_colon_prefix: boolean): string
if not types or #types == 0 then
return ""
@ -57,35 +76,62 @@ local function render_require(dependencies: { string : string }): string
return generated
end
local function render_descendant(descendants: { Node }): string
local generated = ""
for _, descendant in ipairs(descendants) do
generated = generated .. string.format("%s: %s\n", utils.lowercase(descendant.name), descendant.name)
end
return generated
end
local record Node_Printer_Function
on_node: function(node: Node, indent_level: integer): string, integer
before_node: nil | function(node: Node, indent_level: integer): string, integer
after_node: nil | function(node: Node, indent_level: integer): string, integer
on_node: function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
-- optional functions
before_node: function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
after_node: function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
end
-- pre-declare functions to prevent forward reference errors
local print_teal: function(node: Node, indent_level: integer | nil): string
local print_teal: function(node: Node, indent_level: integer, parent_node: Node): string
local print_children: function(node: Node): string
local node_printer <total>: { Node.Token : Node_Printer_Function } = {
["module"] = {
before_node = function(node: Node, indent_level: integer): string, integer
before_node = function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
if "module" == parent_token then
return "", indent_level
end
if node.global then
return render_code(
"-- This file was auto-generated.\n",
GEN_TEXT,
indent_level), indent_level
end
return render_code(
string.format(
"-- This file was auto-generated.\n%s\nlocal record %s",
render_require(node.dependencies), -- last require statement will have a newline
"%s%s\nlocal record %s",
GEN_TEXT,
render_require(
utils.merge_map(
node.dependencies,
utils.pipe(
get_package_descendants,
nodes_to_string_map)(node) as { string : string } -- pipe needs to be promoted
)), -- last require statement will have a newline
node.name),
indent_level), indent_level + 1
end,
on_node = function(node: Node, indent_level: integer): string, integer
on_node = function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
if "module" == parent_token then
return render_code(
render_descendant({ node }),
indent_level), indent_level
end
return render_code(print_children(node), indent_level), indent_level
end,
after_node = function(node: Node, indent_level: integer): string, integer
after_node = function(node: Node, indent_level: integer, parent_token: Node.Token): string, integer
if "module" == parent_token then
return "", indent_level
end
if node.global then
return "", indent_level
end
@ -188,19 +234,22 @@ local node_printer <total>: { Node.Token : Node_Printer_Function } = {
},
}
function print_teal(node: Node, indent_level: integer | nil): string
function print_teal(node: Node,
-- optional parameters
indent_level: integer, parent_node: Node): string
indent_level = indent_level or 0
local parent_node_token = parent_node and parent_node.token
local printer = node_printer[node.token]
local generated = ""
local full_generated = ""
if printer.before_node then
generated, indent_level = (printer.before_node as function(Node, integer): string, integer)(node, indent_level)
generated, indent_level = printer.before_node(node, indent_level, parent_node_token)
full_generated = generated
end
generated, indent_level = printer.on_node(node, indent_level)
generated, indent_level = printer.on_node(node, indent_level, parent_node_token)
full_generated = full_generated .. generated
if printer.after_node then
generated, indent_level = (printer.after_node as function(Node, integer): string, integer)(node, indent_level)
generated, indent_level = printer.after_node(node, indent_level, parent_node_token)
full_generated = full_generated .. generated
end
return full_generated
@ -209,7 +258,7 @@ end
function print_children(node: Node): string
local generated = ""
for _, child in ast.iter_children(node) do
generated = generated .. print_teal(child)
generated = generated .. print_teal(child, 0, node)
end
return generated
end

View File

@ -1,7 +1,7 @@
local type Node = require("awesomewmdtl.types.Node")
local record Dag
modules: { string : Node } -- module_path (AKA "full name" `package.module.name`) -> root_node (token = "module")
modules: { Node } -- list of root modules
global_nodes: { Node }
end

View File

@ -13,6 +13,16 @@ function utils.has_item(t: table, item: any): any
return nil
end
function utils.find<T>(list: { T }, predicate: function(value: T, position: integer): boolean): T, integer
for position, value in ipairs(list) do
if predicate(value, position) then
return value, position
end
end
return nil
end
function utils.filter<T>(list: { T }, predicate: function(value: T, position: integer): boolean): { T }
local filtered: { T } = {}
@ -101,4 +111,27 @@ function utils.pairsByKeys<Key, Value>(list: { Key : Value }, comp: function(Key
end
end
-- We expect the user to type at the call site
function utils.pipe(...: function): function(...: any): any...
local funcs = { ... }
return function(...: any): any...
local res = ...
for _, f in ipairs(funcs) do
res = f(res)
end
return res
end
end
function utils.merge_map<T, U>(t1: { T : U }, t2: { T : U }): { T : U }
local merged: { T : U } = {}
for k, v in pairs(t1) do
merged[k] = v
end
for k, v in pairs(t2) do
merged[k] = v
end
return merged
end
return utils

View File

@ -1,4 +1,5 @@
local type Dag = require("awesomewmdtl.types.Dag")
local dag = require("awesomewmdtl.dag")
local type Node = require("awesomewmdtl.types.Node")
local utils <const> = require("awesomewmdtl.utils")
@ -86,7 +87,7 @@ function Module_Dependencies.visit(node: Node, mod: Node, d: Dag)
goto continue
end
local dependency = d.modules[type_name] or d.modules[utils.lowercase(type_name)] or capi_class[type_name]
local dependency = dag.find_module(d, type_name) or dag.find_module(d, utils.lowercase(type_name)) or capi_class[type_name]
if dependency then
if dependency.name ~= mod.name then
mod.dependencies[dependency.name] = dependency.module_path