Inintal commit

This commit is contained in:
2026-02-14 15:05:12 +01:00
commit 625297106e
21 changed files with 75105 additions and 0 deletions
+268
View File
@@ -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