Compare commits

...

3 Commits

Author SHA1 Message Date
Will Hawkins dd0bbbe479 Make Formatter Happy
Continuous Integration / Grammar Tests (push) Failing after 5s
Continuous Integration / Library Tests (push) Failing after 5s
Continuous Integration / Library Format Tests (push) Failing after 6s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-04 22:25:51 -04:00
Will Hawkins 7a36ca32dd compiler, runtime: Support Formatted Error Messages
Make it possible to output formatted error messages using a
flexible API that includes an ability to specify styles and
formatters.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-04 22:24:28 -04:00
Will Hawkins 783aac26c7 compiler: Use New Error API to Accumulate Errors
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-04 22:23:52 -04:00
10 changed files with 266 additions and 46 deletions
+3 -1
View File
@@ -22,7 +22,9 @@ import Darwin
@main @main
struct Cli: ParsableCommand { struct Cli: ParsableCommand {
public func run() throws { public func run() throws {
let formatter = FormatterPlain()
let e = ErrorWithLocation(sourceLocation: SourceLocation(1, 5), withError: "Testing") let e = ErrorWithLocation(sourceLocation: SourceLocation(1, 5), withError: "Testing")
print(e.format()) let e1 = ErrorWithLocation(sourceLocation: SourceLocation(10, 5), withError: "Oh no")
print(e.append(error: e1).format(formatter))
} }
} }
+16 -9
View File
@@ -16,8 +16,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
public struct Error: Errorable, Equatable, CustomStringConvertible { public struct Error: Errorable, Equatable, CustomStringConvertible {
public func format() -> String { public func format(_ formatter: any Formattable) -> String {
return self.description return self._msg
} }
public func msg() -> String { public func msg() -> String {
@@ -40,11 +40,10 @@ public struct Error: Errorable, Equatable, CustomStringConvertible {
} }
public struct ErrorWithLocation: Errorable, Equatable, CustomStringConvertible { public struct ErrorWithLocation: Errorable, Equatable, CustomStringConvertible {
let startFormat: String = "\u{1B}[31;1;4m" public func format(_ formatter: any Formattable) -> String {
let endFormat: String = "\u{1B}[0m" let bold_red = Style(StyleColor.Red, [StyleFormat.Bold])
let formatted_location = formatter.formatWithStyle(self.location.description, bold_red)
public func format() -> String { return formatted_location + ": " + self._msg
return startFormat + "\(self.location)" + endFormat + ": \(self._msg)"
} }
public func msg() -> String { public func msg() -> String {
@@ -70,8 +69,10 @@ public struct ErrorWithLocation: Errorable, Equatable, CustomStringConvertible {
} }
public struct Errors: Errorable, CustomStringConvertible { public struct Errors: Errorable, CustomStringConvertible {
public func format() -> String { public func format(_ formatter: any Formattable) -> String {
return self.description self.errors.map { error in
error.format(formatter)
}.joined(separator: "\n")
} }
public func msg() -> String { public func msg() -> String {
@@ -103,6 +104,12 @@ public struct ErrorWithLabel: Errorable {
let label: String let label: String
let error: any Errorable let error: any Errorable
public func format(_ formatter: any Formattable) -> String {
let green = Style(StyleColor.Green)
let formatted_label = formatter.formatWithStyle(self.label, green)
return formatted_label + self.error.format(formatter)
}
public init(_ label: String, _ error: any Errorable) { public init(_ label: String, _ error: any Errorable) {
self.label = label self.label = label
self.error = error self.error = error
+116
View File
@@ -0,0 +1,116 @@
// 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/>.
public enum StyleColor {
case Red
case Blue
case Green
}
public enum StyleFormat {
case Underline
case Bold
}
public struct Style: Equatable {
let color: StyleColor?
let format: [StyleFormat]
public init(_ color: StyleColor?, _ format: [StyleFormat] = []) {
self.color = color
self.format = format
}
public func update(setColor color: StyleColor) -> Style {
return Style(color, self.format)
}
public func removeColor() -> Style {
return Style(nil, self.format)
}
public func update(addFormat format: StyleFormat) -> Style {
return if self.format.contains(format) {
Style(self.color, self.format)
} else {
Style(self.color, self.format + [format])
}
}
public func update(removeFormat format: StyleFormat) -> Style {
let new_format = self.format.filter { existing_format in
existing_format != format
}
return Style(self.color, new_format)
}
public func getColor() -> StyleColor? {
return self.color
}
public func getFormat() -> [StyleFormat] {
return self.format
}
}
public struct FormatterPlain: Formattable {
public init() {}
public func formatWithStyle(_ value: String, _ style: Style) -> String {
return value
}
}
public struct FormatterAnsi: Formattable {
public init() {}
let startFormat: String = "\u{1B}["
let resetFormat: String = "\u{1B}[0m"
let colorMap = [
StyleColor.Red: "31",
StyleColor.Green: "32",
StyleColor.Blue: "34",
]
let styleMap = [
StyleFormat.Underline: "4",
StyleFormat.Bold: "1",
]
public func formatWithStyle(_ value: String, _ style: Style) -> String {
let color =
if let color = style.getColor() {
self.colorMap[color]!
} else {
""
}
let style = style.getFormat().map { format in
String(self.styleMap[format]!)
}.joined(separator: ";")
if color.isEmpty && style.isEmpty {
return value
}
let code = startFormat + color + ((!color.isEmpty && !style.isEmpty) ? ";" : "") + style + "m"
return code + value + resetFormat
}
}
+5 -1
View File
@@ -71,7 +71,7 @@ public protocol ProgramExecutionEvaluator {
} }
public protocol Errorable: CustomStringConvertible { public protocol Errorable: CustomStringConvertible {
func format() -> String func format(_ formatter: Formattable) -> String
func msg() -> String func msg() -> String
func append(error: any Errorable) -> any Errorable func append(error: any Errorable) -> any Errorable
func eq(_ rhs: any Errorable) -> Bool func eq(_ rhs: any Errorable) -> Bool
@@ -83,6 +83,10 @@ extension Errorable {
} }
} }
public protocol Formattable {
func formatWithStyle(_ value: String, _ style: Style) -> String
}
extension ProgramExecutionEvaluator { extension ProgramExecutionEvaluator {
public func ExecuteStatements( public func ExecuteStatements(
_ statements: [EvaluatableStatement], inExecution execution: ProgramExecution _ statements: [EvaluatableStatement], inExecution execution: ProgramExecution
+1 -1
View File
@@ -62,7 +62,7 @@ extension Result: CustomStringConvertible where OKT: CustomStringConvertible {
public var description: String { public var description: String {
switch self { switch self {
case Result.Error(let e): case Result.Error(let e):
return e.format() return e.msg()
case Result.Ok(let o): case Result.Ok(let o):
return "Ok: \(o)" return "Ok: \(o)"
} }
+19 -16
View File
@@ -157,7 +157,7 @@ public struct Parser {
sourceLocation: node.toSourceLocation(), withError: "Did not find expected statements")) sourceLocation: node.toSourceLocation(), withError: "Did not find expected statements"))
} }
var parse_errs: [any Errorable] = Array() var errors: (any Errorable)? = .none
var current_context = context var current_context = context
var parsed_s: [EvaluatableStatement] = Array() var parsed_s: [EvaluatableStatement] = Array()
@@ -169,17 +169,19 @@ public struct Parser {
current_context = updated_context current_context = updated_context
parsed_s.append(parsed_statement) parsed_s.append(parsed_statement)
case .Error(let e): case .Error(let e):
parse_errs.append(e) errors =
if let errors = errors {
errors.append(error: e)
} else {
e
}
} }
} }
if !parse_errs.isEmpty { if let errors = errors {
return Result.Error( return .Error(errors)
Error(
withMessage: parse_errs.map { err in
return String(err.format())
}.joined(separator: ";")))
} }
return Result.Ok((parsed_s, current_context)) return Result.Ok((parsed_s, current_context))
} }
} }
@@ -242,7 +244,7 @@ public struct Parser {
sourceLocation: node.toSourceLocation(), withError: "Missing body of state declaration") sourceLocation: node.toSourceLocation(), withError: "Missing body of state declaration")
)) ))
var parse_errs: [any Errorable] = Array() var errors: (any Errorable)? = .none
var current_context = context var current_context = context
var parsed_s: [EvaluatableStatement] = Array() var parsed_s: [EvaluatableStatement] = Array()
@@ -254,17 +256,18 @@ public struct Parser {
parsed_s = state_statements parsed_s = state_statements
current_context = updated_context current_context = updated_context
case .Error(let error): case .Error(let error):
parse_errs.append(error) errors =
if let errors = errors {
errors.append(error: error)
} else {
error
}
} }
walker.next() walker.next()
} }
if !parse_errs.isEmpty { if let errors = errors {
return Result.Error( return .Error(errors)
Error(
withMessage: parse_errs.map { err in
return String(err.format())
}.joined(separator: ";")))
} }
#MustOr( #MustOr(
+20 -13
View File
@@ -60,7 +60,7 @@ public struct Program {
// Add our FFIs // Add our FFIs
compilation_context = compilation_context.update(newFFIs: ffis) compilation_context = compilation_context.update(newFFIs: ffis)
var errors: [any Errorable] = Array() var errors: (any Errorable)? = .none
// If the caller gave any global instances, add them here. // If the caller gave any global instances, add them here.
if let globalInstances = globalInstances { if let globalInstances = globalInstances {
@@ -90,27 +90,34 @@ public struct Program {
break break
case .Error(let e): case .Error(let e):
found_parser = true found_parser = true
errors.append(e) errors =
if let errors = errors {
errors.append(error: e)
} else {
e
}
break break
} }
} }
// If none of the declaration parsers chose to parse, that's an error, too! // If none of the declaration parsers chose to parse, that's an error, too!
if !found_parser { if !found_parser {
errors.append(
ErrorWithLocation( let no_parser_error = ErrorWithLocation(
sourceLocation: specific_declaration_node.toSourceLocation(), sourceLocation: specific_declaration_node.toSourceLocation(),
withError: "Could not find parser for declaration node" withError: "Could not find parser for declaration node"
)) )
errors =
if let errors = errors {
errors.append(error: no_parser_error)
} else {
no_parser_error
}
} }
} }
if !errors.isEmpty { if let errors = errors {
return Result.Error( return .Error(errors)
Error(
withMessage: errors.map { error in
return error.format()
}.joined(separator: ";")))
} }
// Any of the instances that are in the top-level scope should go into the program! // Any of the instances that are in the top-level scope should go into the program!
+28 -4
View File
@@ -18,8 +18,8 @@
import Common import Common
import Foundation import Foundation
import Macros import Macros
import P4Runtime
import P4Lang import P4Lang
import P4Runtime
import SwiftTreeSitter import SwiftTreeSitter
import Testing import Testing
import TreeSitter import TreeSitter
@@ -28,7 +28,31 @@ import TreeSitterP4
@testable import P4Compiler @testable import P4Compiler
@Test func test_error_with_location_formatting() async throws { @Test func test_error_with_location_formatting() async throws {
let e = ErrorWithLocation(sourceLocation: SourceLocation(1, 5), withError: "There was an error") let formatter = FormatterAnsi()
let e = ErrorWithLocation(sourceLocation: SourceLocation(1, 5), withError: "There was an error")
print(e.format()) let formatted = e.format(formatter)
#expect(formatted == "\u{1B}[31;1m{1, 5}\u{1B}[0m: There was an error")
}
@Test func test_errors_with_location_no_formatting() async throws {
let e = ErrorWithLocation(sourceLocation: SourceLocation(1, 5), withError: "There was an error")
let e1 = ErrorWithLocation(
sourceLocation: SourceLocation(10, 5), withError: "There was another error")
let formatted = e.append(error: e1).format(FormatterPlain())
#expect(formatted == "{1, 5}: There was an error\n{10, 5}: There was another error")
}
@Test func test_errors_with_location_ansi_formatting() async throws {
let e = ErrorWithLocation(sourceLocation: SourceLocation(1, 5), withError: "There was an error")
let e1 = ErrorWithLocation(
sourceLocation: SourceLocation(10, 5), withError: "There was another error")
let formatted = e.append(error: e1).format(FormatterAnsi())
#expect(
formatted
== "\u{1B}[31;1m{1, 5}\u{1B}[0m: There was an error\n\u{1B}[31;1m{10, 5}\u{1B}[0m: There was another error"
)
} }
+57
View File
@@ -0,0 +1,57 @@
// 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
import Foundation
import Macros
import P4Runtime
import P4Lang
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_style_add_format() async throws {
let red = Style(StyleColor.Red)
let bold_red = red.update(addFormat: StyleFormat.Bold)
#expect(bold_red == Style(StyleColor.Red, [StyleFormat.Bold]))
}
@Test func test_style_add_format2() async throws {
let bold_red = Style(StyleColor.Red, [StyleFormat.Bold])
let bold_underline_red = bold_red.update(addFormat: StyleFormat.Underline)
#expect(bold_underline_red == Style(StyleColor.Red, [StyleFormat.Bold, StyleFormat.Underline]))
}
@Test func test_style_remove_format() async throws {
let bold_red = Style(StyleColor.Red, [StyleFormat.Bold])
let red = bold_red.update(removeFormat: StyleFormat.Bold)
#expect(red == Style(StyleColor.Red))
}
@Test func test_style_remove_format2() async throws {
let bold_underline_red = Style(StyleColor.Red, [StyleFormat.Bold, StyleFormat.Underline])
let underline_red = bold_underline_red.update(removeFormat: StyleFormat.Bold)
#expect(underline_red == Style(StyleColor.Red, [StyleFormat.Underline]))
}
+1 -1
View File
@@ -42,7 +42,7 @@ import TreeSitterP4
guard case Result.Error(let e) = err else { guard case Result.Error(let e) = err else {
assert(false, "Expected an error, but had success") assert(false, "Expected an error, but had success")
} }
#expect(e.format().contains("Failed to parse a statement element: Could not parse a P4 type from \(invalid_type_name)")) #expect(e.msg().contains("Failed to parse a statement element: Could not parse a P4 type from \(invalid_type_name)"))
} }
} }