Inintal commit
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
-- Load UDT types from external .plc.json files
|
||||
-- Auto-generate from data_types folder
|
||||
|
||||
local M = {}
|
||||
|
||||
local cached_types = {}
|
||||
|
||||
local function run_command(cmd)
|
||||
local handle = io.popen(cmd)
|
||||
if not handle then return nil end
|
||||
local result = handle:read("*a")
|
||||
handle:close()
|
||||
return result
|
||||
end
|
||||
|
||||
local script_path = debug.getinfo(1, "S").source:gsub("^@", ""):match("(.*/)") or ""
|
||||
if script_path == "" then script_path = "." end
|
||||
|
||||
local cwd = io.popen("pwd"):read("*l")
|
||||
if script_path:sub(1, 1) == "/" then
|
||||
script_path = script_path:gsub("/+$", "")
|
||||
elseif cwd then
|
||||
script_path = cwd .. "/" .. script_path
|
||||
script_path = script_path:gsub("/+", "/"):gsub("/$", "")
|
||||
else
|
||||
script_path = "."
|
||||
end
|
||||
|
||||
local function load_json(content)
|
||||
local json = dofile(script_path .. "/json.lua")
|
||||
local ok, data = pcall(function() return json.decode(content) end)
|
||||
if ok then return data end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function scan_files_recursive(dir, pattern)
|
||||
local files = {}
|
||||
local result = run_command("find " .. dir .. " -type f -name \"" .. pattern .. "\" 2>/dev/null")
|
||||
if result then
|
||||
for f in result:gmatch("[^\n]+") do
|
||||
if f ~= "" then table.insert(files, f) end
|
||||
end
|
||||
end
|
||||
return files
|
||||
end
|
||||
|
||||
local function parse_plc_type(dt)
|
||||
local type_name = dt.name or dt.Name or dt.baseType or dt.dataTypeName
|
||||
if not type_name then return nil end
|
||||
|
||||
local typ = {
|
||||
name = type_name,
|
||||
kind = "alias",
|
||||
fields = {},
|
||||
source = "external",
|
||||
}
|
||||
|
||||
if dt.struct or dt.member or dt.members or dt.elements then
|
||||
typ.kind = "struct"
|
||||
local members = dt.struct or dt.member or dt.members or dt.elements
|
||||
if type(members) == "table" then
|
||||
for _, m in ipairs(members) do
|
||||
local field_name = m.name or m.Name
|
||||
local field_type = m.dataTypeName or m.baseType or m.type or m.dataType or "unknown"
|
||||
if field_name then
|
||||
table.insert(typ.fields, {
|
||||
name = field_name,
|
||||
type = field_type,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif dt.baseType or dt.baseTypeName then
|
||||
typ.kind = "alias"
|
||||
typ.base_type = dt.baseType or dt.baseTypeName
|
||||
end
|
||||
|
||||
return typ
|
||||
end
|
||||
|
||||
local function parse_scl_type_file(content)
|
||||
local types = {}
|
||||
local in_type = false
|
||||
local in_struct = false
|
||||
local current_type_name = nil
|
||||
local pending_type_name = nil
|
||||
|
||||
local lines = {}
|
||||
for line in content:gmatch("[^\n]+") do table.insert(lines, line) end
|
||||
|
||||
for i, line in ipairs(lines) do
|
||||
local trimmed = line:gsub("^%s+", ""):gsub("%s+$", "")
|
||||
|
||||
if trimmed:match("^TYPE%s*:?%s*$") then
|
||||
in_type = true
|
||||
in_struct = false
|
||||
current_type_name = nil
|
||||
pending_type_name = nil
|
||||
elseif trimmed:match("^END_TYPE") then
|
||||
in_type = false
|
||||
in_struct = false
|
||||
current_type_name = nil
|
||||
pending_type_name = nil
|
||||
elseif in_type then
|
||||
if trimmed:match("^END_STRUCT") then
|
||||
in_struct = false
|
||||
current_type_name = nil
|
||||
pending_type_name = nil
|
||||
elseif trimmed:match("^STRUCT") then
|
||||
if pending_type_name then
|
||||
current_type_name = pending_type_name
|
||||
in_struct = true
|
||||
types[pending_type_name] = { name = pending_type_name, kind = "struct", fields = {}, source = "data_types_folder" }
|
||||
pending_type_name = nil
|
||||
end
|
||||
elseif not in_struct and trimmed:match("^%w+%s*:") and not trimmed:match("^STRUCT") then
|
||||
local type_name = trimmed:match("^(%w+)%s*:")
|
||||
if type_name then
|
||||
local next_line = lines[i + 1]
|
||||
local next_trimmed = next_line and next_line:gsub("^%s+", ""):gsub("%s+$", "")
|
||||
if next_trimmed and next_trimmed:match("^STRUCT") then
|
||||
pending_type_name = type_name
|
||||
else
|
||||
pending_type_name = nil
|
||||
current_type_name = nil
|
||||
in_struct = false
|
||||
local base_type = trimmed:match(":%s*([%w_]+)")
|
||||
types[type_name] = { name = type_name, kind = "alias", base_type = base_type or "UNKNOWN", source = "data_types_folder" }
|
||||
end
|
||||
end
|
||||
elseif in_struct and current_type_name and types[current_type_name] then
|
||||
local field_name = line:match("^%s*(%w+)%s*:")
|
||||
if field_name and field_name ~= "END_STRUCT" then
|
||||
local field_type = line:match(":%s*([%w_]+)")
|
||||
table.insert(types[current_type_name].fields, {
|
||||
name = field_name,
|
||||
type = field_type or "unknown",
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return types
|
||||
end
|
||||
|
||||
function M.load_types_from_workspace(root_dir)
|
||||
local types = {}
|
||||
|
||||
if not root_dir or root_dir == "" then return types end
|
||||
|
||||
-- Scan for .plc.json files
|
||||
local plc_files = scan_files_recursive(root_dir, "*.plc.json")
|
||||
for _, filepath in ipairs(plc_files) do
|
||||
local content = run_command("cat " .. filepath:gsub(" ", "\\ "))
|
||||
if content then
|
||||
local data = load_json(content)
|
||||
if data and (data.dataTypes or data.DataTypes or data.types or data.Types) then
|
||||
local typeList = data.dataTypes or data.DataTypes or data.types or data.Types
|
||||
for _, dt in ipairs(typeList) do
|
||||
local typ = parse_plc_type(dt)
|
||||
if typ and not types[typ.name] then
|
||||
types[typ.name] = typ
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Scan for plc.data.json files
|
||||
local plc_data_files = scan_files_recursive(root_dir, "plc.data.json")
|
||||
for _, filepath in ipairs(plc_data_files) do
|
||||
local content = run_command("cat " .. filepath:gsub(" ", "\\ "))
|
||||
if content then
|
||||
local data = load_json(content)
|
||||
if data and (data.dataTypes or data.DataTypes) then
|
||||
for _, dt in ipairs(data.dataTypes or data.DataTypes) do
|
||||
local typ = parse_plc_type(dt)
|
||||
if typ and not types[typ.name] then
|
||||
types[typ.name] = typ
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Auto-generate from data_types folder (SCL files)
|
||||
local data_types_files = scan_files_recursive(root_dir, "*.scl")
|
||||
for _, filepath in ipairs(data_types_files) do
|
||||
local content = run_command("cat " .. filepath:gsub(" ", "\\ "))
|
||||
if content then
|
||||
local scl_types = parse_scl_type_file(content)
|
||||
for name, typ in pairs(scl_types) do
|
||||
if not types[name] then
|
||||
types[name] = typ
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return types
|
||||
end
|
||||
|
||||
function M.get_types(root_dir)
|
||||
if not root_dir or root_dir == "" then
|
||||
root_dir = "workspace"
|
||||
end
|
||||
|
||||
if cached_types[root_dir] then
|
||||
return cached_types[root_dir]
|
||||
end
|
||||
|
||||
local types = M.load_types_from_workspace(root_dir)
|
||||
cached_types[root_dir] = types
|
||||
return types
|
||||
end
|
||||
|
||||
function M.generate_plc_json(root_dir, output_file)
|
||||
local json = dofile(script_path .. "/json.lua")
|
||||
|
||||
if not root_dir or root_dir == "" then return false end
|
||||
|
||||
local types = M.load_types_from_workspace(root_dir)
|
||||
|
||||
local dataTypes = {}
|
||||
for _, typ in pairs(types) do
|
||||
if typ.kind == "struct" then
|
||||
local members = {}
|
||||
for _, field in ipairs(typ.fields) do
|
||||
table.insert(members, {
|
||||
name = field.name,
|
||||
dataTypeName = field.type,
|
||||
})
|
||||
end
|
||||
table.insert(dataTypes, {
|
||||
name = typ.name,
|
||||
struct = members,
|
||||
})
|
||||
elseif typ.kind == "alias" then
|
||||
table.insert(dataTypes, {
|
||||
name = typ.name,
|
||||
baseType = typ.base_type or "UNKNOWN",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local output = {
|
||||
plcProject = {
|
||||
dataTypes = dataTypes,
|
||||
},
|
||||
}
|
||||
|
||||
local content = json.encode(output)
|
||||
local file = io.open(output_file, "w")
|
||||
if file then
|
||||
file:write(content)
|
||||
file:close()
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function M.clear_cache()
|
||||
cached_types = {}
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user