feat: diagnostics & parser spec conformance (Phase 5)
diagnostics.lua: - Add VAR DB_SPECIFIC to section detection - Make all VAR/END_VAR detection case-insensitive (matches grammar) - Replace 20-line hardcoded type check list with builtin.is_known_data_type() and builtin.is_known_type_or_instruction() — now uses the expanded DATA_TYPES table from Phase 4 instead of inline string comparisons - Add attribute validation: warn on unknown per-variable attributes (SCL005). Valid attributes: ExternalWritable, ExternalVisible, ExternalAccessible, S7_SetPoint, S7_Optimized_Access - Allow optional semicolon after END_VAR parser.lua: - Add VAR DB_SPECIFIC to section detection and var_type classification - Make all block detection case-insensitive (FUNCTION_BLOCK, FUNCTION, ORGANIZATION_BLOCK, END_FUNCTION_BLOCK, etc.) - Make TYPE/END_TYPE, STRUCT/END_STRUCT, VAR/END_VAR case-insensitive - Fix VAR RETAIN vs VAR NON_RETAIN classification (NON_RETAIN was matching the RETAIN branch first) - Allow optional semicolon after END_VAR and END_STRUCT formatter.lua: - Add VAR DB_SPECIFIC and VAR_DB_SPECIFIC to BLOCKS and VAR_KEYWORDS - Fix get_keyword() to handle multi-word VAR keywords (VAR NON_RETAIN, VAR DB_SPECIFIC, VAR CONSTANT, VAR RETAIN) — normalizes spaces to underscores so the keyword lookup succeeds - Fix is_new_statement detection for space-form VAR keywords All 38 formatter tests pass. Parser and diagnostics load without errors.
This commit is contained in:
+31
-28
@@ -24,8 +24,8 @@ function M.extract_variables(content)
|
||||
local function process_line(line, line_num)
|
||||
local trimmed = line:gsub("^%s+", ""):gsub("%s+$", "")
|
||||
|
||||
-- Detect block start (with or without quotes)
|
||||
local fb_name = trimmed:match('FUNCTION_BLOCK%s+"([^"]+)"') or trimmed:match("^FUNCTION_BLOCK%s+([%w_]+)")
|
||||
-- Detect block start (with or without quotes) — case-insensitive
|
||||
local fb_name = trimmed:match('[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]_[Bb][Ll][Oo][Cc][Kk]%s+"([^"]+)"') or trimmed:match("^[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]_[Bb][Ll][Oo][Cc][Kk]%s+([%w_]+)")
|
||||
if fb_name then
|
||||
current_block = { name = fb_name, kind = "function_block" }
|
||||
in_var_section = false
|
||||
@@ -33,7 +33,7 @@ function M.extract_variables(content)
|
||||
return
|
||||
end
|
||||
|
||||
local ob_name = trimmed:match('ORGANIZATION_BLOCK%s+"([^"]+)"') or trimmed:match("^ORGANIZATION_BLOCK%s+([%w_]+)")
|
||||
local ob_name = trimmed:match('[Oo][Rr][Gg][Aa][Nn][Ii][Zz][Aa][Tt][Ii][Oo][Nn]_[Bb][Ll][Oo][Cc][Kk]%s+"([^"]+)"') or trimmed:match("^[Oo][Rr][Gg][Aa][Nn][Ii][Zz][Aa][Tt][Ii][Oo][Nn]_[Bb][Ll][Oo][Cc][Kk]%s+([%w_]+)")
|
||||
if ob_name then
|
||||
current_block = { name = ob_name, kind = "organization_block" }
|
||||
in_var_section = false
|
||||
@@ -41,7 +41,7 @@ function M.extract_variables(content)
|
||||
return
|
||||
end
|
||||
|
||||
local fn_name = trimmed:match('FUNCTION%s+"([^"]+)"') or trimmed:match("^FUNCTION%s+([%w_]+)")
|
||||
local fn_name = trimmed:match('[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]%s+"([^"]+)"') or trimmed:match("^[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]%s+([%w_]+)")
|
||||
if fn_name then
|
||||
current_block = { name = fn_name, kind = "function" }
|
||||
in_var_section = false
|
||||
@@ -49,60 +49,63 @@ function M.extract_variables(content)
|
||||
return
|
||||
end
|
||||
|
||||
if trimmed:match("^END_FUNCTION_BLOCK") or
|
||||
trimmed:match("^END_ORGANIZATION_BLOCK") or
|
||||
trimmed:match("^END_FUNCTION") then
|
||||
if trimmed:upper():match("^END_FUNCTION_BLOCK") or
|
||||
trimmed:upper():match("^END_ORGANIZATION_BLOCK") or
|
||||
trimmed:upper():match("^END_FUNCTION") then
|
||||
current_block = nil
|
||||
in_var_section = false
|
||||
in_struct_section = false
|
||||
return
|
||||
end
|
||||
|
||||
-- Handle TYPE sections (for .udt files with STRUCT)
|
||||
if trimmed:match("^TYPE") and (trimmed:match("^TYPE%s") or trimmed:match('^TYPE"') or trimmed == "TYPE") then
|
||||
-- Handle TYPE sections (for .udt files with STRUCT) — case-insensitive
|
||||
if trimmed:upper():match("^TYPE") and (trimmed:upper():match("^TYPE%s") or trimmed:upper():match('^TYPE"') or trimmed:upper() == "TYPE") then
|
||||
in_struct_section = true
|
||||
var_type = "STRUCT_FIELD"
|
||||
return
|
||||
end
|
||||
|
||||
if trimmed:match("^END_TYPE") then
|
||||
if trimmed:upper():match("^END_TYPE") then
|
||||
in_struct_section = false
|
||||
return
|
||||
end
|
||||
|
||||
if trimmed:match("^VAR[_A-Z]*%s*$") or
|
||||
trimmed:match("^VAR_INPUT%s*$") or
|
||||
trimmed:match("^VAR_OUTPUT%s*$") or
|
||||
trimmed:match("^VAR_IN_OUT%s*$") or
|
||||
trimmed:match("^VAR_TEMP%s*$") or
|
||||
trimmed:match("^VAR[_ ]*CONSTANT%s*$") or
|
||||
trimmed:match("^VAR[_ ]*RETAIN%s*$") or
|
||||
trimmed:match("^VAR[_ ]*NON_RETAIN%s*$") then
|
||||
if trimmed:upper():match("^VAR[_A-Z]*%s*$") or
|
||||
trimmed:upper():match("^VAR_INPUT%s*$") or
|
||||
trimmed:upper():match("^VAR_OUTPUT%s*$") or
|
||||
trimmed:upper():match("^VAR_IN_OUT%s*$") or
|
||||
trimmed:upper():match("^VAR_TEMP%s*$") or
|
||||
trimmed:upper():match("^VAR[_ ]*CONSTANT%s*$") or
|
||||
trimmed:upper():match("^VAR[_ ]*RETAIN%s*$") or
|
||||
trimmed:upper():match("^VAR[_ ]*NON_RETAIN%s*$") or
|
||||
trimmed:upper():match("^VAR[_ ]*DB_SPECIFIC%s*$") then
|
||||
in_var_section = true
|
||||
in_struct_section = false
|
||||
if trimmed:match("INPUT") then var_type = "VAR_INPUT"
|
||||
elseif trimmed:match("OUTPUT") then var_type = "VAR_OUTPUT"
|
||||
elseif trimmed:match("IN_OUT") then var_type = "VAR_IN_OUT"
|
||||
elseif trimmed:match("TEMP") then var_type = "VAR_TEMP"
|
||||
elseif trimmed:match("CONSTANT") then var_type = "VAR_CONSTANT"
|
||||
elseif trimmed:match("RETAIN") then var_type = "VAR_RETAIN"
|
||||
elseif trimmed:match("NON_RETAIN") then var_type = "VAR_NON_RETAIN"
|
||||
local upper_trimmed = trimmed:upper()
|
||||
if upper_trimmed:match("INPUT") then var_type = "VAR_INPUT"
|
||||
elseif upper_trimmed:match("OUTPUT") then var_type = "VAR_OUTPUT"
|
||||
elseif upper_trimmed:match("IN_OUT") then var_type = "VAR_IN_OUT"
|
||||
elseif upper_trimmed:match("TEMP") then var_type = "VAR_TEMP"
|
||||
elseif upper_trimmed:match("CONSTANT") then var_type = "VAR_CONSTANT"
|
||||
elseif upper_trimmed:match("RETAIN") and not upper_trimmed:match("NON_RETAIN") then var_type = "VAR_RETAIN"
|
||||
elseif upper_trimmed:match("NON_RETAIN") then var_type = "VAR_NON_RETAIN"
|
||||
elseif upper_trimmed:match("DB_SPECIFIC") then var_type = "VAR_DB_SPECIFIC"
|
||||
else var_type = "VAR" end
|
||||
return
|
||||
end
|
||||
|
||||
if trimmed:match("^END_VAR%s*$") then
|
||||
if trimmed:upper():match("^END_VAR%s*;?%s*$") then
|
||||
in_var_section = false
|
||||
return
|
||||
end
|
||||
|
||||
if trimmed:match("^STRUCT%s*$") then
|
||||
if trimmed:upper():match("^STRUCT%s*$") then
|
||||
in_struct_section = true
|
||||
var_type = "STRUCT_FIELD"
|
||||
return
|
||||
end
|
||||
|
||||
if trimmed:match("^END_STRUCT%s*$") then
|
||||
if trimmed:upper():match("^END_STRUCT%s*;?%s*$") then
|
||||
in_struct_section = false
|
||||
return
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user