Skip to content

Commit eb58324

Browse files
committed
Better parsing error messages - now with code context and arrows!
1 parent 7aee537 commit eb58324

File tree

3 files changed

+164
-116
lines changed

3 files changed

+164
-116
lines changed

src/functions.lua2p

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,8 @@ do
184184
["["] = "]",
185185
}
186186

187-
-- position, isUrlBlock = findEndOfBlockContents( path, template, position )
188-
local function findEndOfBlockContents(path, template, pos)
187+
-- position, isUrlBlock = findEndOfBlockContents( path, template, position, blockI1 )
188+
local function findEndOfBlockContents(path, template, pos, blockI1)
189189
!local PRINT_PROGRESS = DEV and 1==0
190190
!if PRINT_PROGRESS then __LUA`printf("%s(%d):%d: >>>>>>>>", path, pos, getLineNumber(template, pos))` end
191191

@@ -197,7 +197,7 @@ do
197197
if template:find("^ *%.?%.?/", pos) or template:find("^ *%a%w*:/", pos) then
198198
local i1 = template:find("%*?}}", pos)
199199
if not i1 then
200-
fileError(path, template, pos, "Incomplete URL block.")
200+
fileError(path, template, blockI1, "Incomplete URL block.")
201201
end
202202
return i1-1, true
203203
end
@@ -249,7 +249,7 @@ do
249249

250250
if not i2 then
251251
fileError(
252-
path, template, pos,
252+
path, template, posComment,
253253
"Incomplete long comment. (Code block starting at line %d)",
254254
getLineNumber(template, blockContentsI1)
255255
)
@@ -296,7 +296,7 @@ do
296296

297297
if not i2 then
298298
fileError(
299-
path, template, pos,
299+
path, template, posStringLong,
300300
"Incomplete long string. (Code block starting at line %d)",
301301
getLineNumber(template, blockContentsI1)
302302
)
@@ -354,11 +354,7 @@ do
354354
)
355355
end
356356

357-
fileError(
358-
path, template, pos,
359-
"Missing end of code block. (Code block starting at line %d)",
360-
getLineNumber(template, blockContentsI1)
361-
)
357+
fileError(path, template, blockI1, "Missing end of this code block.")
362358
end
363359
end
364360

@@ -394,7 +390,7 @@ do
394390

395391
table.insert(out, "do end ") -- Statement divider.
396392

397-
local blockContentsI2, isUrlBlock = findEndOfBlockContents(path, template, pos)
393+
local blockContentsI2, isUrlBlock = findEndOfBlockContents(path, template, pos, blockI1)
398394
local blockContents = template:sub(blockContentsI1, blockContentsI2)
399395
pos = blockContentsI2 + 1
400396

@@ -431,13 +427,13 @@ do
431427
if not expr then ident, expr = blockContents:match!(NOSPACE("^ %s* fori %s+ ("..PATTERN_IDENT..") %s+ in "..PATTERN_IDENT_END.." %s* (%S.*)"))
432428
if not expr then reverse, expr = blockContents:match!(NOSPACE("^ %s* fori %s* (<?) %s* (%S.*)"))
433429
if not expr then
434-
fileError(path, template, blockI1, "Invalid 'fori' statement.")
430+
fileError(path, template, blockContentsI1, "Invalid 'fori' statement.")
435431
end end end
436432

437433
reverse = (reverse == "<")
438434

439435
if not loadstring("_=("..expr..")") then
440-
fileError(path, template, blockI1, "Invalid value expression for 'fori' loop: %s", trim(expr)) -- @UX: Better error message.
436+
fileError(path, template, blockContentsI1, "Invalid value expression for 'fori' loop: %s", trim(expr)) -- @UX: Better error message.
441437
end
442438

443439
table.insert(out, "for i, ")
@@ -492,7 +488,7 @@ do
492488
local expr = blockContents:gsub("for%s*<", "", 1)
493489

494490
if not loadstring("_=("..expr..")") then
495-
fileError(path, template, blockI1, "Invalid value expression for simplified 'for' loop: %s", trim(expr)) -- @UX: Better error message.
491+
fileError(path, template, blockContentsI1, "Invalid value expression for simplified 'for' loop: %s", trim(expr)) -- @UX: Better error message.
496492
end
497493

498494
table.insert(out, "for i = (")
@@ -504,7 +500,7 @@ do
504500
local expr = blockContents:gsub("for", "", 1)
505501

506502
if not loadstring("_=("..expr..")") then
507-
fileError(path, template, blockI1, "Invalid value expression for simplified 'for' loop: %s", trim(expr)) -- @UX: Better error message.
503+
fileError(path, template, blockContentsI1, "Invalid value expression for simplified 'for' loop: %s", trim(expr)) -- @UX: Better error message.
508504
end
509505

510506
table.insert(out, "for i = 1, (")
@@ -625,7 +621,7 @@ do
625621
then
626622
-- @Polish: Make sure there's no garbage at the end of blockContents.
627623
if tcsLevel == 1 then
628-
fileError(path, template, blockI1, "Unexpected templateified '%s'.", blockContents:match"%a+")
624+
fileError(path, template, blockContentsI1, "Unexpected templateified '%s'.", blockContents:match"%a+")
629625
end
630626
!!(TRIM_POS_AFTER_BLOCK)
631627
local blockI2 = pos - 1
@@ -634,7 +630,7 @@ do
634630
-- Value expression.
635631
elseif insertLuaForEchoingIfExpression(out, blockContents) then
636632
if blockContents:find"^%s*function%s*%(" then -- We do allow echoing functions, but this specifically is probably an error made by the user.
637-
fileError(path, template, blockI1, "Invalid value expression.")
633+
fileError(path, template, blockContentsI1, "Invalid value expression.")
638634
end
639635
!!(TRIM_POS_AFTER_BLOCK)
640636

@@ -802,16 +798,57 @@ function _G.errorf(levelOrS, ...)
802798
end
803799
end
804800

805-
-- fileError( path, contents, position, formatString, ... )
806-
-- fileError( path, nil, lineNumber, formatString, ... )
807-
function _G.fileError(path, contents, pos, s, ...)
808-
local ln = contents and getLineNumber(contents, pos) or pos
809-
if type(s) ~= "string" then
810-
s = F("%s:%d: %s", path, ln, tostring(s))
811-
else
812-
s = F("%s:%d: "..s, path, ln, ...)
801+
do
802+
local function findStartOfNonEmptyLine(s, pos)
803+
while pos > 1 do
804+
if s:byte(pos-1) == !(string.byte"\n") and s:byte(pos) ~= !(string.byte"\n") then break end
805+
pos = pos - 1
806+
end
807+
return math.max(pos, 1)
808+
end
809+
local function findEndOfLine(s, pos)
810+
while pos < #s do
811+
if s:byte(pos+1) == !(string.byte"\n") then break end
812+
pos = pos + 1
813+
end
814+
return math.min(pos, #s)
815+
end
816+
817+
-- fileError( path, contents, position, formatString, ... )
818+
-- fileError( path, nil, lineNumber, formatString, ... )
819+
function _G.fileError(path, contents, pos, s, ...)
820+
if contents then
821+
pos = math.min(math.max(pos, 1), #contents+1)
822+
end
823+
local ln = contents and getLineNumber(contents, pos) or pos
824+
825+
if type(s) ~= "string" then
826+
s = F("%s:%d: %s", path, ln, tostring(s))
827+
else
828+
s = F("%s:%d: "..s, path, ln, ...)
829+
end
830+
831+
if contents then
832+
local lineStart = findStartOfNonEmptyLine(contents, pos)
833+
local lineEnd = findEndOfLine(contents, pos-1)
834+
local linePre1Start = findStartOfNonEmptyLine(contents, lineStart-1)
835+
local linePre1End = findEndOfLine(contents, linePre1Start-1)
836+
local linePre2Start = findStartOfNonEmptyLine(contents, linePre1Start-1)
837+
local linePre2End = findEndOfLine(contents, linePre2Start-1)
838+
printf("pos %d | lines %d..%d, %d..%d, %d..%d", pos, linePre2Start,linePre2End+1, linePre1Start,linePre1End+1, lineStart,lineEnd+1)
839+
840+
s = F("%s\n\n%s%s> %s\n> %s^",
841+
s,
842+
(linePre2Start < linePre1Start and linePre2Start <= linePre2End) and F("> %s\n", (contents:sub(linePre2Start, linePre2End):gsub("\t", " "))) or "",
843+
(linePre1Start < lineStart and linePre1Start <= linePre1End) and F("> %s\n", (contents:sub(linePre1Start, linePre1End):gsub("\t", " "))) or "",
844+
contents:sub(lineStart, lineEnd):gsub("\t", " "),
845+
("-"):rep(pos - lineStart + 3*countSubStrings(contents, lineStart, lineEnd, "\t", true)),
846+
nil
847+
)
848+
end
849+
850+
errorLine(s)
813851
end
814-
errorLine(s)
815852
end
816853

817854
-- errorLine( message )

0 commit comments

Comments
 (0)