compiler, cli: Support Nice Compilation Error Messages
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
@@ -96,7 +96,8 @@ extension Cli {
|
||||
|
||||
let maybe_program = Program.Compile(source.getSource())
|
||||
guard case .Ok(_) = maybe_program else {
|
||||
print(ErrorWithLabel("Compiler Error", maybe_source.error()!).format(formatter))
|
||||
let feedback = CompilationFeedback(source, [maybe_program.error()!], formatter)
|
||||
print(feedback.feedback)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,29 @@ public struct Error: Errorable, Equatable, CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
public func trimmingPostfix(_ in_: String.Element) -> String {
|
||||
return String(self.reversed().drop { $0 == in_ }.reversed())
|
||||
}
|
||||
}
|
||||
|
||||
public struct ErrorWithLocation: Errorable, Equatable, CustomStringConvertible {
|
||||
|
||||
public func format(_ formatter: any Formattable, _ sc: SourceCode) -> String {
|
||||
guard let (fp, source, prior, after) = sc.getSourceSnippet(location: self.location, context: 5)
|
||||
else {
|
||||
return self.format(formatter)
|
||||
}
|
||||
|
||||
let prior_snipped = prior.trimmingPrefix(["\n"])
|
||||
let after_snipped = after.prefix { $0 != "\n" }
|
||||
return formatter.formatWithStyle("Error: ", Style(StyleColor.Red))
|
||||
+ "In \(fp), there was an error: \n..." + prior_snipped
|
||||
+ formatter.formatWithStyle(source, Style(.none, [StyleFormat.Underline])) + after_snipped
|
||||
+ "...\n"
|
||||
+ self._msg
|
||||
}
|
||||
|
||||
public func format(_ formatter: any Formattable) -> String {
|
||||
let bold_red = Style(StyleColor.Red, [StyleFormat.Bold])
|
||||
let formatted_location = formatter.formatWithStyle(self.location.description, bold_red)
|
||||
|
||||
@@ -72,6 +72,7 @@ public protocol ProgramExecutionEvaluator {
|
||||
|
||||
public protocol Errorable: CustomStringConvertible {
|
||||
func format(_ formatter: Formattable) -> String
|
||||
func format(_ formatter: Formattable, _ sc: SourceCode) -> String
|
||||
func msg() -> String
|
||||
func append(error: any Errorable) -> any Errorable
|
||||
func eq(_ rhs: any Errorable) -> Bool
|
||||
@@ -81,6 +82,9 @@ extension Errorable {
|
||||
public func eq(_ rhs: any Errorable) -> Bool {
|
||||
return self.msg() == rhs.msg()
|
||||
}
|
||||
public func format(_ formatter: Formattable, _ sc: SourceCode) -> String {
|
||||
return self.format(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol Formattable {
|
||||
|
||||
@@ -210,6 +210,33 @@ public struct SourceCode {
|
||||
return self.code
|
||||
}
|
||||
|
||||
public func getSourceSnippet(
|
||||
location: SourceLocation, context: Int = 0
|
||||
) -> (FilePath, String, String, String)? {
|
||||
guard let path = self.pathForLocation(location.range.lowerBound) else {
|
||||
return .none
|
||||
}
|
||||
let lower = String.UTF8View.Index(utf16Offset: location.range.lowerBound, in: self.code)
|
||||
let upper = String.UTF8View.Index(utf16Offset: location.range.upperBound, in: self.code)
|
||||
let prior_start =
|
||||
if location.range.lowerBound - context >= 0 {
|
||||
String.UTF8View.Index(utf16Offset: location.range.lowerBound - context, in: self.code)
|
||||
} else {
|
||||
String.UTF8View.Index(utf16Offset: location.range.lowerBound, in: self.code)
|
||||
}
|
||||
let after_end =
|
||||
if location.range.upperBound + context < self.code.count {
|
||||
String.UTF8View.Index(utf16Offset: location.range.upperBound + context, in: self.code)
|
||||
} else {
|
||||
String.UTF8View.Index(utf16Offset: location.range.upperBound, in: self.code)
|
||||
}
|
||||
|
||||
let result = String(self.code.utf16[lower..<upper])!
|
||||
let prior = String(self.code.utf16[prior_start..<lower])!
|
||||
let after = String(self.code.utf16[upper...after_end])!
|
||||
return (path, result, prior, after)
|
||||
}
|
||||
|
||||
public func getLocations() -> FileSourceLocation {
|
||||
return self.locations
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// p4rse, Copyright 2026, Will Hawkins
|
||||
//
|
||||
// This file is part of p4rse.
|
||||
//
|
||||
// This file is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import Common
|
||||
|
||||
public struct CompilationFeedback {
|
||||
public let feedback: String
|
||||
|
||||
static func markup(
|
||||
_ source: SourceCode, _ errors: [any Errorable], _ formatter: any Formattable
|
||||
) -> String {
|
||||
errors.map { $0.format(formatter, source) }.joined(separator: "\n")
|
||||
}
|
||||
|
||||
public init(_ source: SourceCode, _ errors: [any Errorable], _ formatter: any Formattable) {
|
||||
self.feedback = Self.markup(source, errors, formatter)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
control simple(int x, int y) {
|
||||
action a(inout bool aa, int ax, inout bool ay) {
|
||||
aa = false;
|
||||
}
|
||||
table t {
|
||||
key = {
|
||||
x: exact;
|
||||
y: exact;
|
||||
}
|
||||
}
|
||||
apply {
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
Error: In /Users/hawkinsw/code/p4ce/TestData/Sources/action-parameters-wrong-order.p4, there was an error:
|
||||
...ion a(inout bool aa, int ax, inout bool ay) {...
|
||||
All parameters with direction must precede directionless parameters
|
||||
@@ -38,4 +38,9 @@ func simple_cli_preprocessor_test() -> [String] {
|
||||
@CliTest()
|
||||
func simple_cli_preprocessor_test_file_not_found() -> [String] {
|
||||
return ["p4ce", "--plain", "preprocess", "simple.p", "-I", "TestData/Sources/"]
|
||||
}
|
||||
|
||||
@CliTest()
|
||||
func simple_cli_compilation_error() -> [String] {
|
||||
return ["p4ce", "--plain", "compile", "action-parameters-wrong-order.p4", "-I", "TestData/Sources/"]
|
||||
}
|
||||
@@ -136,7 +136,8 @@ import TreeSitterP4
|
||||
#expect(
|
||||
#RequireErrorResult(
|
||||
Error(
|
||||
withMessage: "{54, 63}: Error(s) parsing property list: {91, 26}: Error(s) parsing table actions: Cannot find b in lexical scope."
|
||||
withMessage:
|
||||
"{54, 63}: Error(s) parsing property list: {91, 26}: Error(s) parsing table actions: Cannot find b in lexical scope."
|
||||
),
|
||||
Program.Compile(simple_parser_declaration))
|
||||
)
|
||||
@@ -163,7 +164,8 @@ import TreeSitterP4
|
||||
#expect(
|
||||
#RequireErrorResult(
|
||||
Error(
|
||||
withMessage: "{54, 72}: Error(s) parsing property list: {91, 35}: Error(s) parsing table actions: Cannot find b in lexical scope."
|
||||
withMessage:
|
||||
"{54, 72}: Error(s) parsing property list: {91, 35}: Error(s) parsing table actions: Cannot find b in lexical scope."
|
||||
),
|
||||
Program.Compile(simple_parser_declaration))
|
||||
)
|
||||
@@ -190,7 +192,8 @@ import TreeSitterP4
|
||||
#expect(
|
||||
#RequireErrorResult(
|
||||
Error(
|
||||
withMessage: "{64, 63}: Error(s) parsing property list: {101, 26}: Error(s) parsing table actions: {101, 26}: a does not name an action"
|
||||
withMessage:
|
||||
"{64, 63}: Error(s) parsing property list: {101, 26}: Error(s) parsing table actions: {101, 26}: a does not name an action"
|
||||
),
|
||||
Program.Compile(simple_parser_declaration))
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user