peon-rewrite/src/util/fmterr.nim

91 lines
3.7 KiB
Nim

# Copyright 2024 Mattia Giambirtone & All Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
## Utilities to format peon exceptions into human-readable error messages
## and print them
import frontend/compiler/typechecker
import frontend/parsing/parser
import frontend/parsing/lexer
import errors
export errors
import std/os
import std/terminal
import std/strutils
import std/strformat
proc formatError*(errKind: string = "", outFile = stderr, file, line: string, lineNo: int, pos: tuple[start, stop: int], fn: Declaration, msg: string, includeSource = true) =
## Helper to write a formatted error message to the given file object
if errKind == "":
outFile.styledWrite(fgRed, styleBright, "Error in ", fgYellow, &"{file}:{lineNo}:{pos.start}")
else:
outFile.styledWrite(fgRed, styleBright, &"{errKind} error in ", fgYellow, &"{file}:{lineNo}:{pos.start}")
if not fn.isNil() and fn.kind == funDecl:
# Error occurred inside a (named) function
stderr.styledWrite(fgRed, styleBright, " in function ", fgYellow, FunDecl(fn).name.token.lexeme)
outFile.styledWriteLine(styleBright, fgDefault, ": ", msg)
if line.len() > 0 and includeSource:
# Print the line where the error occurred and underline the exact node that caused
# the error. Might be inaccurate, but definitely better than nothing
outFile.styledWrite(fgRed, styleBright, "Source line: ", resetStyle, fgDefault, line[0..<pos.start])
outFile.styledWrite(fgRed, styleUnderscore, line[pos.start..pos.stop])
if pos.stop + 1 <= line.high():
outFile.styledWriteLine(fgDefault, line[pos.stop + 1..^1])
else:
outFile.styledWriteLine(fgDefault, "")
proc print*(exc: TypeCheckError, includeSource = true) =
## Prints a formatted error message
## for type checking errors to stderr
var file = exc.file
var contents = ""
case exc.line:
of -1: discard
of 0: contents = exc.instance.getSource().strip(chars={'\n'}).splitLines()[exc.line]
else: contents = exc.instance.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1]
formatError("Type", stderr, file, contents, exc.line, exc.node.getRelativeBoundaries(), exc.function, exc.msg, includeSource)
proc print*(exc: ParseError, includeSource = true) =
## Prints a formatted error message
## for parsing errors to stderr
var file = exc.file
if file notin ["<string>", ""]:
file = relativePath(exc.file, getCurrentDir())
var contents = ""
if exc.line != -1:
contents = exc.parser.getSource().strip(chars={'\n'}).splitLines()[exc.line - 1]
else:
contents = ""
formatError("Parsing", stderr, file, contents, exc.line, exc.token.relPos, nil, exc.msg, includeSource)
proc print*(exc: LexingError, includeSource = true) =
## Prints a formatted error message
## for lexing errors to stderr
var file = exc.file
if file notin ["<string>", ""]:
file = relativePath(exc.file, getCurrentDir())
var contents = ""
if exc.line != -1:
contents = exc.lexer.getSource().splitLines()[exc.line - 1]
else:
contents = ""
formatError("Parsing", stderr, file, contents, exc.line, exc.pos, nil, exc.msg, includeSource)