Untitled
unknown
lua
11 days ago
11 kB
6
Indexable
function checkSyntax(code, filename) local errors = {} filename = filename or "<string>" local lines = {} local line_ends = {} -- --- Phần tiền xử lý --- if type(code) ~= "string" then table.insert(errors, string.format("%s:0:0: Input code must be a string, got %s", filename, type(code))) return errors end do local pos = 1 for line_content, newline_sequence in code:gmatch("([^\r\n]*)(\r?\n?)") do table.insert(lines, line_content) local line_len = #line_content; local nl_len = #newline_sequence pos = pos + line_len + nl_len; table.insert(line_ends, pos - 1) end local code_len = #code if (#line_ends == 0 and code_len > 0) or (#line_ends > 0 and line_ends[#line_ends] < code_len) then local last_line_start = (#line_ends > 0 and line_ends[#line_ends] + 1) or 1 if last_line_start <= code_len then table.insert(lines, code:sub(last_line_start)); table.insert(line_ends, code_len) end elseif #lines == 0 and code_len == 0 then table.insert(lines, ""); table.insert(line_ends, 0) end end -- --- Kết thúc tiền xử lý --- -- --- Helper Functions --- local function getLineNum(char_pos) local low = 1; local high = #line_ends; local line_num = #line_ends while low <= high do local mid = math.floor((low + high) / 2); local start_of_line = (mid == 1 and 0) or line_ends[mid - 1] if start_of_line < 0 then start_of_line = 0 end -- Ensure start_of_line is not negative if char_pos > start_of_line and char_pos <= line_ends[mid] then line_num = mid; break elseif char_pos <= start_of_line then high = mid - 1 else low = mid + 1 end end -- Handle edge case where char_pos is before the first recorded end if #line_ends > 0 and char_pos <= (line_ends[1] or 0) then line_num = 1 end if #lines == 0 then return 1 end return math.max(1, math.min(line_num, #lines)) end local function getLineStartPos(line_num) if line_num <= 1 then return 1 end line_num = math.max(1, math.min(line_num, #line_ends + 1)) -- Check if the previous line index exists if line_num > 1 and (line_num - 1) <= #line_ends then -- Ensure the value retrieved is a number before adding 1 local prev_end_pos = line_ends[line_num - 1] if type(prev_end_pos) == "number" then return prev_end_pos + 1 else -- Fallback if line_ends is somehow corrupted return 1 end else -- Fallback if line_num is out of expected range (e.g., line_num = #line_ends + 1) if #line_ends > 0 and type(line_ends[#line_ends]) == "number" then return line_ends[#line_ends] + 1 else return 1 -- Safest fallback for empty or corrupted line_ends end end end local function addError(char_pos, msg) local line_num = getLineNum(char_pos) local line_start = getLineStartPos(line_num) -- Ensure line_start is valid before calculating col_num if line_start > char_pos then line_start = 1 end -- Safety check local col_num = char_pos - line_start + 1 col_num = math.max(1, col_num) table.insert(errors, string.format("%s:%d:%d: %s", filename, line_num, col_num, msg)) end local function addErrorAtLineEnd(line_num, col, msg) line_num = math.max(1, math.min(line_num, #lines)) col = math.max(1, col) table.insert(errors, string.format("%s:%d:%d: %s", filename, line_num, col, msg)) end -- --- End Helper Functions --- -- --- Parser State and Definitions --- local stack = {}; local i = 1; local len = #code local openPairs = { ["("] = ")", ["["] = "]", ["{"] = "}" }; local pairClosers = { [")"] = "(", ["]"] = "[", ["}"] = "{" } local blockOpeners = { ["function"]=true, ["if"]=true, ["for"]=true, ["while"]=true, ["repeat"]=true } -- no 'do' local blockEnders = { ["end"]=true, ["until"]=true } local blockModifiers = { ["else"]=true, ["elseif"]=true } local endCanClose = {["function"]=true, ["if"]=true, ["for"]=true, ["while"]=true} -- no 'do' local untilCanClose = {["repeat"]=true} -- --- End Definitions --- -- --- Main Parsing Loop --- while i <= len do local initial_i = i local handled = false local c1 = code:sub(i, i) local c2 = code:sub(i, i + 1) -- 1. Comments if c2 == '--' then handled = true; local start_comment_pos = i; local current_i = i + 2; local c3 = code:sub(current_i, current_i); local c4 = code:sub(current_i + 1, current_i + 1) if c3 == '[' and (c4 == '[' or c4 == '=') then local level = 0; local start_bracket = current_i; while code:sub(start_bracket + 1 + level, start_bracket + 1 + level) == '=' do level = level + 1 end if code:sub(start_bracket + level + 1, start_bracket + level + 1) == '[' then local closer_str = ']' .. ('='):rep(level) .. ']'; local search_start = start_bracket + level + 2; local end_comment = code:find(closer_str, search_start, true) if end_comment then local closer_len = #closer_str; i = end_comment + closer_len else addError(start_comment_pos, "unfinished long comment"); i = len + 1 end else i = start_comment_pos + 2; while i <= len do local cc = code:sub(i,i); if cc == '\n' or cc == '\r' then i = i + 1; break end; i = i + 1 end end else i = current_i; while i <= len do local cc = code:sub(i,i); if cc == '\n' or cc == '\r' then i = i + 1; break end; i = i + 1 end end -- 2. Strings elseif c1 == '"' or c1 == "'" then handled = true; local quote = c1; local start_string_pos = i; i = i + 1; local closed = false; while i <= len do local nc = code:sub(i, i); if nc == '\\' then i = i + 2 elseif nc == quote then i = i + 1; closed = true; break elseif nc == '\n' or nc == '\r' then addError(start_string_pos, "malformed string (newline encountered)"); closed = true; break else i = i + 1 end end; if not closed then addError(start_string_pos, "unfinished string") end -- 3. Long Strings elseif c2 == '[[' or (c1 == '[' and code:sub(i + 1, i + 1) == '=') then local start_string_pos = i; local level = 0; local start_bracket = i; while code:sub(start_bracket + 1 + level, start_bracket + 1 + level) == '=' do level = level + 1 end if code:sub(start_bracket + level + 1, start_bracket + level + 1) == '[' then handled = true; local closer_str = ']' .. ('='):rep(level) .. ']'; local search_start = start_bracket + level + 2; local end_string = code:find(closer_str, search_start, true) if end_string then local closer_len = #closer_str; i = end_string + closer_len else addError(start_string_pos, "unfinished long string"); i = len + 1 end end end -- 4. Syntax Characters and Keywords if not handled then if openPairs[c1] then local line, col = getLineNum(i), i - getLineStartPos(getLineNum(i)) + 1; table.insert(stack, { type = c1, line = line, col = col, pos = i }); i = i + 1 elseif pairClosers[c1] then local line, col = getLineNum(i), i - getLineStartPos(getLineNum(i)) + 1; local last = stack[#stack]; if last and last.type == pairClosers[c1] then table.remove(stack) else local expected = (last and openPairs[last.type]) and string.format(" (expected '%s' to close '%s' at %d:%d)", openPairs[last.type], last.type, last.line, last.col) or ""; addError(i, string.format("unexpected symbol '%s'%s", c1, expected)) end; i = i + 1 elseif c1:match("[%a_]") then local word_start_pos = i; local word_end_pos = word_start_pos; while word_end_pos <= len and code:sub(word_end_pos, word_end_pos):match("[%w_]") do word_end_pos = word_end_pos + 1 end; local word = code:sub(word_start_pos, word_end_pos - 1); local word_line, word_col = getLineNum(word_start_pos), word_start_pos - getLineStartPos(getLineNum(word_start_pos)) + 1 if blockOpeners[word] then table.insert(stack, { type = word, line = word_line, col = word_col, pos = word_start_pos }) elseif blockEnders[word] then local last = stack[#stack]; if word == "end" then if last and endCanClose[last.type] then table.remove(stack) else if not last then addError(word_start_pos, string.format("'%s' unexpected (no open block)", word)) else addError(word_start_pos, string.format("'%s' unexpected (cannot close block type '%s' opened at %d:%d)", word, last.type, last.line, last.col)) end end elseif word == "until" then if last and untilCanClose[last.type] then table.remove(stack) else local expected_type = (last and last.type) or "nothing"; addError(word_start_pos, string.format("'until' unexpected (expected 'repeat', found '%s' at %d:%d or stack empty)", expected_type, last and last.line or 0, last and last.col or 0)) end end elseif blockModifiers[word] then local last = stack[#stack]; if not last or last.type ~= "if" then addError(word_start_pos, string.format("'%s' unexpected (not immediately inside an 'if' block)", word)) end end i = word_end_pos elseif c1:match("%s") then i = i + 1 else i = i + 1 end end if i == initial_i and i <= len then addError(i, "Internal parser error: loop stuck"); break end end -- --- End Main Parsing Loop --- -- --- Final Stack Check --- for idx, item in ipairs(stack) do local expected = "?"; if openPairs[item.type] then expected = "'"..openPairs[item.type].."'" elseif endCanClose[item.type] then expected = "'end'" elseif untilCanClose[item.type] then expected = "'until'" end local err_line = #lines; local err_col = 1; if err_line > 0 and #lines[err_line] > 0 then err_col = #lines[err_line] + 1 end if openPairs[item.type] then addError(item.pos, string.format("%s expected (to close '%s' starting at %d:%d)", expected, item.type, item.line, item.col)) else addErrorAtLineEnd(err_line, err_col, string.format("%s expected (to close '%s' starting at %d:%d)", expected, item.type, item.line, item.col)) end end -- --- End Final Stack Check --- return errors end
Editor is loading...
Leave a Comment