Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f9fbb86bf | |||
| 16a798cc39 | |||
| 017d5670c0 | |||
| d60465e669 | |||
| b3ca30541a |
@@ -56,25 +56,4 @@ jobs:
|
||||
- run: tree-sitter generate
|
||||
working-directory: ./tree-sitter-p4
|
||||
- run: ./ci/format.sh
|
||||
cli-tests:
|
||||
name: Cli Tests
|
||||
runs-on: ubuntu-build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: tree-sitter/setup-action@v2
|
||||
with:
|
||||
tree-sitter-ref: "master"
|
||||
- uses: swift-actions/setup-swift@v3
|
||||
with:
|
||||
swift-version: "main-snapshot"
|
||||
skip-verify-signature: true
|
||||
# Because our tree-sitter code is in a subdirectory,
|
||||
# and working-directory does not apply to uses, we
|
||||
# are forced to specify calls to the CI ourselves.
|
||||
# See https://github.com/orgs/community/discussions/25742
|
||||
- run: tree-sitter generate
|
||||
working-directory: ./tree-sitter-p4
|
||||
- run: swift build
|
||||
- run: ./ci/tests/test.sh
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
6.2.4
|
||||
main-snapshot
|
||||
|
||||
+31
-1
@@ -27,7 +27,7 @@ struct Cli: ParsableCommand {
|
||||
|
||||
static let configuration = CommandConfiguration(
|
||||
abstract: "P4CE compiler, interpreter and debugger.",
|
||||
subcommands: [Compile.self, CodeGen.self])
|
||||
subcommands: [Preprocess.self, Compile.self, CodeGen.self])
|
||||
}
|
||||
|
||||
struct CliOptions: ParsableArguments {
|
||||
@@ -38,6 +38,36 @@ struct CliOptions: ParsableArguments {
|
||||
var search: [String] = []
|
||||
}
|
||||
|
||||
extension Cli {
|
||||
struct Preprocess: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Compile P4CE code.")
|
||||
|
||||
@ParentCommand var parent: Cli
|
||||
|
||||
@OptionGroup var options: CliOptions
|
||||
|
||||
mutating func run() {
|
||||
let sm = SourceManager(options.search.map { FilePath($0) })
|
||||
let prep = SourceCodePreprocessor(sm)
|
||||
let file = FilePath(options.path)
|
||||
|
||||
let formatter: any Formattable =
|
||||
if parent.plain != 0 {
|
||||
FormatterPlain()
|
||||
} else {
|
||||
FormatterAnsi()
|
||||
}
|
||||
|
||||
let maybe_source = prep.preprocess(file)
|
||||
guard case .Ok(let source) = maybe_source else {
|
||||
print(ErrorWithLabel("Preprocessor Error", maybe_source.error()!).format(formatter))
|
||||
return
|
||||
}
|
||||
|
||||
print(source.getSource())
|
||||
}
|
||||
}
|
||||
}
|
||||
extension Cli {
|
||||
struct Compile: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Compile P4CE code.")
|
||||
|
||||
+103
-37
@@ -291,6 +291,77 @@ public struct MustOr: CodeItemMacro {
|
||||
|
||||
public struct CliTestDeclarationMacro: PeerMacro, Sendable {
|
||||
|
||||
// NOTE: Taken from swift-testing.
|
||||
|
||||
/// Get an expression initializing an instance of ``SourceLocation`` from an
|
||||
/// arbitrary syntax node.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - node: The syntax node for which an instance of ``SourceLocation`` is
|
||||
/// needed.
|
||||
/// - context: The macro context in which the expression is being parsed.
|
||||
///
|
||||
/// - Returns: An expression value that initializes an instance of
|
||||
/// ``SourceLocation`` for `node`.
|
||||
static func createSourceLocationExpr(
|
||||
of node: some SyntaxProtocol, context: some MacroExpansionContext
|
||||
) -> ExprSyntax {
|
||||
if node.isProtocol((any FreestandingMacroExpansionSyntax).self) {
|
||||
// Freestanding macro expressions can just use __here()
|
||||
// directly and do not need to talk to the macro context to get source
|
||||
// location info.
|
||||
return "Testing.SourceLocation.__here()"
|
||||
}
|
||||
|
||||
// Get the equivalent source location in both `#fileID` and `#filePath` modes.
|
||||
guard let fileIDSourceLoc: AbstractSourceLocation = context.location(of: node),
|
||||
let filePathSourceLoc: AbstractSourceLocation = context.location(
|
||||
of: node, at: .afterLeadingTrivia, filePathMode: .filePath)
|
||||
else {
|
||||
return "Testing.SourceLocation.__here()"
|
||||
}
|
||||
|
||||
return
|
||||
"Testing.SourceLocation(__uncheckedFileID: \(fileIDSourceLoc.file), filePath: \(filePathSourceLoc.file), line: \(fileIDSourceLoc.line), column: \(fileIDSourceLoc.column))"
|
||||
}
|
||||
|
||||
/// Get an expression initializing an instance of `__SourceBounds` from two
|
||||
/// arbitrary syntax nodesvalues.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lowerBoundNode: The syntax node representing the lower bound. The start
|
||||
/// of this node (after leading trivia) is used.
|
||||
/// - upperBoundNode: The syntax node representing the upper bound. The end of
|
||||
/// this node (before trailing trivia) is used.
|
||||
/// - context: The macro context in which the expression is being parsed.
|
||||
///
|
||||
/// - Returns: An expression value that initializes an instance of
|
||||
/// `__SourceBounds`.
|
||||
///
|
||||
/// The resulting source bounds instance represents (approximately):
|
||||
///
|
||||
/// ```swift
|
||||
/// lowerBoundNode.positionAfterSkippingLeadingTrivia ..< upperBoundNode.endPositionBeforeTrailingTrivia
|
||||
/// ```
|
||||
static func createSourceBoundsExpr(
|
||||
from lowerBoundNode: some SyntaxProtocol, to upperBoundNode: some SyntaxProtocol,
|
||||
in context: some MacroExpansionContext
|
||||
) -> ExprSyntax {
|
||||
let lowerBoundExpr = createSourceLocationExpr(of: lowerBoundNode, context: context)
|
||||
let upperBoundExpr: ExprSyntax =
|
||||
if let upperBoundSourceLoc = context.location(
|
||||
of: upperBoundNode, at: .beforeTrailingTrivia, filePathMode: .fileID)
|
||||
{
|
||||
"(\(upperBoundSourceLoc.line), \(upperBoundSourceLoc.column))"
|
||||
} else {
|
||||
"(.max, .max)"
|
||||
}
|
||||
return
|
||||
"Testing.__SourceBounds(__uncheckedLowerBound: \(lowerBoundExpr), upperBound: \(upperBoundExpr))"
|
||||
}
|
||||
|
||||
// NOTE: End of what was taken from swift-testing
|
||||
|
||||
private static func doc_shrink(_ from: String) -> String {
|
||||
return from.replacing(Regex(#/^.*\/\/\/[\s]+/#), with: "")
|
||||
}
|
||||
@@ -301,74 +372,69 @@ public struct CliTestDeclarationMacro: PeerMacro, Sendable {
|
||||
in context: some MacroExpansionContext
|
||||
) throws -> [DeclSyntax] {
|
||||
|
||||
let test_name = declaration.cast(FunctionDeclSyntax.self).name
|
||||
|
||||
let cli_test_expected_output = node.leadingTrivia.filter({ $0.isComment }).map({
|
||||
doc_shrink("\($0)")
|
||||
}).joined(separator: "\\n")
|
||||
|
||||
let cli_test_driver_thunk_name = context.makeUniqueName(
|
||||
declaration.cast(FunctionDeclSyntax.self).name.text + "_thunk_")
|
||||
let cli_test_driver_thunk_name = context.makeUniqueName("_thunk_")
|
||||
|
||||
let (expected_decl, expected_label) =
|
||||
if cli_test_expected_output.isEmpty {
|
||||
("let expected = \"\(test_name)\"", "withExpectedPath:")
|
||||
} else {
|
||||
("let expected = \"\(cli_test_expected_output)\"", "withExpected:")
|
||||
}
|
||||
|
||||
let cli_test_driver_thunk: DeclSyntax = """
|
||||
@Sendable private func \(cli_test_driver_thunk_name)() async throws {
|
||||
let expected = "\(raw: cli_test_expected_output)"
|
||||
\(raw: expected_decl)
|
||||
|
||||
_ = unsafe try await Testing.__requiringUnsafe(
|
||||
Testing.__requiringTry(
|
||||
Testing.__requiringAwait(swiftCliTestRunner(\(declaration.cast(FunctionDeclSyntax.self).name), expected))))
|
||||
Testing.__requiringAwait(swiftCliTestRunner(\(test_name), \(raw: expected_label) expected))))
|
||||
}
|
||||
"""
|
||||
|
||||
let cli_test_driver_generator_name = context.makeUniqueName(
|
||||
declaration.cast(FunctionDeclSyntax.self).name.text + "_generator_")
|
||||
let source_bounds = createSourceBoundsExpr(from: node, to: declaration, in: context)
|
||||
let cli_test_driver_generator_name = context.makeUniqueName("_generator_")
|
||||
let cli_test_driver_generator: DeclSyntax = """
|
||||
@Sendable private func \(cli_test_driver_generator_name)() async -> Testing.Test {
|
||||
return .__function(
|
||||
named: "xxxxxxxx()",
|
||||
in: nil,
|
||||
xcTestCompatibleSelector: Testing.__xcTestCompatibleSelector("xxxxxx:"),
|
||||
named: "\(test_name)",
|
||||
in: nil as Swift.Never.Type?,
|
||||
xcTestCompatibleSelector: Testing.__xcTestCompatibleSelector("\(test_name)"),
|
||||
traits: [],
|
||||
sourceLocation: Testing.SourceLocation(
|
||||
fileID: "Tests/CliTests/Cli.swift",
|
||||
filePath: "/Users/hawkinsw/code/p4ce/Tests/p4rseTests/CliTests/Cli.swift", line: 359,
|
||||
column: 2),
|
||||
sourceBounds: \(source_bounds),
|
||||
parameters: [],
|
||||
testFunction: \(cli_test_driver_thunk_name)
|
||||
)
|
||||
}
|
||||
"""
|
||||
|
||||
let cli_test_driver_accessor_name = context.makeUniqueName(
|
||||
declaration.cast(FunctionDeclSyntax.self).name.text + "_accessor_")
|
||||
let cli_test_driver_accessor: DeclSyntax = """
|
||||
private nonisolated let \(cli_test_driver_accessor_name): Accessor = { outValue, type, _, _ in
|
||||
#if os(macOS)
|
||||
let section = "__DATA_CONST,__swift5_tests"
|
||||
#else
|
||||
let section = "swift5_tests"
|
||||
#endif
|
||||
|
||||
Testing.Test.__store(\(cli_test_driver_generator_name), into: outValue, asTypeAt: type)
|
||||
}
|
||||
"""
|
||||
|
||||
let cli_test_driver_content_record_name = context.makeUniqueName(
|
||||
declaration.cast(FunctionDeclSyntax.self).name.text + "_cr_")
|
||||
let cli_test_driver_content_record_name = context.makeUniqueName("testContentRecord")
|
||||
let cli_test_driver_cr: DeclSyntax = """
|
||||
private nonisolated let \(cli_test_driver_content_record_name): TestContentRecord = (
|
||||
@section("\(raw: section)")
|
||||
@used
|
||||
private nonisolated let \(cli_test_driver_content_record_name): Testing.__TestContentRecord = (
|
||||
0x7465_7374, /* indicate a test */
|
||||
0,
|
||||
unsafe \(cli_test_driver_accessor_name),
|
||||
{ outValue, type, _, _ in
|
||||
Testing.Test.__store(\(cli_test_driver_generator_name), into: outValue, asTypeAt: type)
|
||||
},
|
||||
0,
|
||||
0
|
||||
)
|
||||
"""
|
||||
|
||||
let cli_test_driver_content_container_name = context.makeUniqueName(
|
||||
declaration.cast(FunctionDeclSyntax.self).name.text + "__🟡$_container_")
|
||||
let cli_test_driver_container: DeclSyntax = """
|
||||
struct \(cli_test_driver_content_container_name): Testing.__TestContentRecordContainer {
|
||||
nonisolated static var __testContentRecord: TestContentRecord {
|
||||
unsafe \(cli_test_driver_content_record_name)
|
||||
}
|
||||
}
|
||||
"""
|
||||
return [
|
||||
cli_test_driver_thunk, cli_test_driver_generator, cli_test_driver_accessor,
|
||||
cli_test_driver_cr, cli_test_driver_container,
|
||||
cli_test_driver_thunk, cli_test_driver_generator, cli_test_driver_cr,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
parser main_parser() {
|
||||
state start {
|
||||
transition select (false) {
|
||||
true: reject;
|
||||
false: reject;
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
Preprocessor Error: Could not open simple.p for preprocessing
|
||||
@@ -0,0 +1 @@
|
||||
Success
|
||||
+15
-2
@@ -16,8 +16,12 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import Foundation
|
||||
import SystemPackage
|
||||
import Testing
|
||||
|
||||
private let TestGoldenPath: FilePath.Component = FilePath.Component("TestData")
|
||||
private let TestGoldenExtension: String = ".golden"
|
||||
|
||||
typealias Accessor =
|
||||
@convention(c) (
|
||||
_ outValue: UnsafeMutableRawPointer,
|
||||
@@ -68,7 +72,6 @@ func swiftRun(withArgs args: [String]) throws -> String? {
|
||||
let path = swiftPath()!
|
||||
let child = Process()
|
||||
let so = Pipe()
|
||||
let se = Pipe()
|
||||
|
||||
child.standardOutput = so
|
||||
child.executableURL = path
|
||||
@@ -90,7 +93,7 @@ func swiftRun(withArgs args: [String]) throws -> String? {
|
||||
return output
|
||||
}
|
||||
|
||||
func swiftCliTestRunner(_ arg_gen: () -> [String], _ expected: String) async throws {
|
||||
func swiftCliTestRunner(_ arg_gen: () -> [String], withExpected expected: String) async throws {
|
||||
let args = arg_gen()
|
||||
|
||||
let run_output = try! swiftRun(withArgs: args)
|
||||
@@ -98,7 +101,17 @@ func swiftCliTestRunner(_ arg_gen: () -> [String], _ expected: String) async thr
|
||||
#expect(run_output == expected)
|
||||
}
|
||||
|
||||
func swiftCliTestRunner(_ arg_gen: () -> [String], withExpectedPath expected_path: String) async throws {
|
||||
let args = arg_gen()
|
||||
|
||||
let run_output = try! swiftRun(withArgs: args)
|
||||
|
||||
let expected_source_absolute = FilePath(FileManager().currentDirectoryPath).appending(TestGoldenPath).appending(expected_path + TestGoldenExtension)
|
||||
|
||||
let expected_output = try! String(contentsOfFile: expected_source_absolute.string)
|
||||
|
||||
#expect(run_output == expected_output)
|
||||
}
|
||||
|
||||
@attached(peer) public macro CliTest() =
|
||||
#externalMacro(module: "Macros", type: "CliTestDeclarationMacro")
|
||||
@@ -24,3 +24,18 @@ import Testing
|
||||
func simple_cli_test() -> [String] {
|
||||
return ["p4ce", "--plain", "compile", "simple.p4", "-I", "TestData/Sources/"]
|
||||
}
|
||||
|
||||
@CliTest()
|
||||
func simple_cli_testa() -> [String] {
|
||||
return ["p4ce", "--plain", "compile", "simple.p4", "-I", "TestData/Sources/"]
|
||||
}
|
||||
|
||||
@CliTest()
|
||||
func simple_cli_preprocessor_test() -> [String] {
|
||||
return ["p4ce", "--plain", "preprocess", "simple.p4", "-I", "TestData/Sources/"]
|
||||
}
|
||||
|
||||
@CliTest()
|
||||
func simple_cli_preprocessor_test_file_not_found() -> [String] {
|
||||
return ["p4ce", "--plain", "preprocess", "simple.p", "-I", "TestData/Sources/"]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
actual_output=`swift run p4ce --plain compile simple.p4 -I TestData/Sources/`
|
||||
expected_output=`cat TestData/Cli/simple.golden`
|
||||
if [ "${expected_output}" != "${actual_output}" ]; then
|
||||
echo "Expected: ${expected_output}"
|
||||
echo "Actual: ${actual_output}"
|
||||
echo "Cli Tests: Error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Cli Tests: Success"
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user