compiler, cli: Support Nice Compilation Error Messages

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-05-22 02:37:33 -04:00
parent 5845cb75cc
commit e53c32f802
9 changed files with 115 additions and 4 deletions
+2 -1
View File
@@ -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
}
+22
View File
@@ -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)
+4
View File
@@ -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 {
+27
View File
@@ -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
}
+33
View File
@@ -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
+5
View File
@@ -39,3 +39,8 @@ func simple_cli_preprocessor_test() -> [String] {
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/"]
}
+6 -3
View File
@@ -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))
)