From d6d5cc8df735db363f27d3f760fa72940c86a944 Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Fri, 23 Jan 2026 23:30:28 -0500 Subject: [PATCH] Runtime Work Signed-off-by: Will Hawkins --- Package.swift | 16 +- Sources/P4/Parser.swift | 61 ++++++-- Sources/P4/Program.swift | 73 ++++++++- Sources/P4/Runtime.swift | 71 ++++----- Sources/P4/Support.swift | 55 +++++++ Sources/P4/Types.swift | 18 ++- Sources/P4Macros/Macros.swift | 85 ++++++++++ Sources/Parser/Parser.swift | 240 ++++++++++++++++++++--------- Tests/p4lmTests/ParserTests.swift | 8 +- Tests/p4lmTests/RuntimeTests.swift | 42 +++-- 10 files changed, 532 insertions(+), 137 deletions(-) create mode 100644 Sources/P4/Support.swift create mode 100644 Sources/P4Macros/Macros.swift diff --git a/Package.swift b/Package.swift index fb7c551..c561c57 100644 --- a/Package.swift +++ b/Package.swift @@ -1,10 +1,12 @@ // swift-tools-version: 6.2 // The swift-tools-version declares the minimum version of Swift required to build this package. +import CompilerPluginSupport import PackageDescription let package = Package( name: "p4lm", + platforms: [ .iOS(.v17), .macOS(.v13)], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( @@ -14,14 +16,21 @@ let package = Package( .library( name: "P4", targets: ["P4"] - ) + ), ], dependencies: [ .package(path: "./tree-sitter-p4"), .package(url: "https://github.com/tree-sitter/swift-tree-sitter", revision: "main"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), + .package(url: "https://github.com/swiftlang/swift-syntax", from: "509.0.0"), ], targets: [ + .macro( + name: "P4Macros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + ]), // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( @@ -30,17 +39,18 @@ let package = Package( .product(name: "SwiftTreeSitter", package: "swift-tree-sitter"), .product(name: "SwiftTreeSitterLayer", package: "swift-tree-sitter"), .product(name: "TreeSitterP4", package: "tree-sitter-p4"), - .target(name: "P4") + .target(name: "P4"), ], ), // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( name: "P4", + dependencies: ["P4Macros"] ), .testTarget( name: "ParserTests", - dependencies: ["Parser", "P4"] + dependencies: ["Parser", "P4", "P4Macros"] ), ] ) diff --git a/Sources/P4/Parser.swift b/Sources/P4/Parser.swift index 7dbe7a5..c43ae2e 100644 --- a/Sources/P4/Parser.swift +++ b/Sources/P4/Parser.swift @@ -6,7 +6,7 @@ public struct LocalElement { } -public struct ParserExecution { +public class ParserExecution: ProgramExecution { public var state: ParserState public init(_ state: ParserState) { @@ -14,7 +14,13 @@ public struct ParserExecution { } public func transition(toNextState state: ParserState) -> ParserExecution { - return ParserExecution(state) + let next = self + next.state = state + return next + } + + public override var description: String { + return "Execution: \(super.description)\nCurrent State: \(state)" } } @@ -26,7 +32,7 @@ public protocol Expression { func evaluate(execution: ParserExecution) -> Value } -public protocol ParserStatement: Sendable { +public protocol ParserStatement { /// Evaluate a statement for a given execution /// - Parameters /// - execution: The execution context in which to evaluate the parser statement @@ -37,6 +43,20 @@ public protocol ParserStatement: Sendable { public struct ParserTransitionStatement: ParserStatement { public init() {} public func evaluate(execution: ParserExecution) -> ParserExecution { + return execution + } +} + +public struct VariableDeclarationStatement: ParserStatement { + public var id: Identifier + public init(withIdentifier id: Identifier) { + self.id = id + } + + public func evaluate(execution: ParserExecution) -> ParserExecution { + print("Evaluating!") + execution.scopes.scopes[0].variables.append(Variable(name: id.name, withValue: id.value, isConstant: false)) + print("Execution: \(execution)") return execution } } @@ -48,32 +68,46 @@ public struct ExpressionStatement: ParserStatement { } } -public struct ParserState: Equatable, Sendable { +public struct ParserState: Equatable, CustomStringConvertible { + public private(set) var state_name: String + public private(set) var local_elements: [ParserStatement] public private(set) var statements: [ParserStatement] public private(set) var transition: ParserTransitionStatement? + public var description: String { + return "Name: \(state_name)" + } + public static func == (lhs: ParserState, rhs: ParserState) -> Bool { return lhs.state_name == rhs.state_name } /// Construct a ParserState - public init(name: String, withStatements statements: [ParserStatement]?, withTransition transitionStatement: ParserTransitionStatement) { + public init(name: String, withLocalElements localElements: [ParserStatement]?, withStatements statements: [ParserStatement]?, withTransition transitionStatement: ParserTransitionStatement) { state_name = name transition = transitionStatement + local_elements = localElements ?? Array() self.statements = statements ?? Array() } func evaluate(execution: ParserExecution) -> ParserExecution { var currentExecution = execution + + // First, evaluate the local elements. + for local_element in local_elements { + currentExecution = local_element.evaluate(execution: currentExecution) + } + + // Then, evaluate the statements. for statement in statements { currentExecution = statement.evaluate(execution: currentExecution) } return if let transition = transition { - execution.transition(toNextState: accept) + currentExecution.transition(toNextState: accept) } else { - execution.transition(toNextState: reject) + currentExecution.transition(toNextState: reject) } } @@ -83,18 +117,19 @@ public struct ParserState: Equatable, Sendable { init(name: String) { state_name = name transition = .none + local_elements = Array() statements = Array() } } -public struct ParserStates: Sendable { +public struct ParserStates { public var states: [ParserState] = Array() } -public let accept: ParserState = ParserState(name: "accept") -public let reject: ParserState = ParserState(name: "reject") +nonisolated(unsafe) public let accept: ParserState = ParserState(name: "accept") +nonisolated(unsafe) public let reject: ParserState = ParserState(name: "reject") -public struct Parser { +public struct Parser: CustomStringConvertible { public var states: [ParserState] = Array() public var count: Int { states.count @@ -110,4 +145,8 @@ public struct Parser { } return .none } + + public var description: String { + return "Parser" + } } diff --git a/Sources/P4/Program.swift b/Sources/P4/Program.swift index f933fe5..345c76c 100644 --- a/Sources/P4/Program.swift +++ b/Sources/P4/Program.swift @@ -1,4 +1,71 @@ -public struct Program { +public class Identifier: CustomStringConvertible { + var name: String + var value: Value + + public init(name: String, withValue value: Value) { + self.name = name + self.value = value + } + + public var description: String { + return "\(name) = \(value)" + } +} + +public class Variable: Identifier { + var constant: Bool + + public init(name: String, withValue value: Value, isConstant constant: Bool) { + self.constant = constant + super.init(name: name, withValue: value) + } + + public override var description: String { + return "\(super.description) \(constant ? "(constant)" : "")" + } +} + +public struct Scope: CustomStringConvertible{ + var variables: [Variable] = Array() + public init() {} + + public var description: String { + var result = String() + for v in variables { + result += "\(v)" + } + return result + } +} + +public struct Scopes: CustomStringConvertible { + var scopes: [Scope] = Array() + + public init() {} + + public mutating func enter() { + scopes.append(Scope()) + } + + public mutating func exit() { + let _ = scopes.popLast() + } + + public var description: String { + var result = String() + for s in scopes { + result += "Scope: \(s)\n" + } + + return result + } +} + +public struct Program: CustomStringConvertible { public var parsers: [P4.Parser] = Array() - public init() { } -} \ No newline at end of file + public init() {} + + public var description: String { + return "Program" + } +} diff --git a/Sources/P4/Runtime.swift b/Sources/P4/Runtime.swift index 3f41101..94b2954 100644 --- a/Sources/P4/Runtime.swift +++ b/Sources/P4/Runtime.swift @@ -1,40 +1,41 @@ -public struct Error { - public private(set) var msg: String +public class ProgramExecution: CustomStringConvertible { + public var scopes: Scopes = Scopes() - public init(withMessage msg: String) { - self.msg = msg - } -} - -public enum Result: Equatable { - case Ok - case Error(Error) - - public static func == (lhs: Result, rhs: Result) -> Bool { - switch (lhs, rhs) { - case (Ok, Ok): - return true - case (Error(let le), Error(let re)): - return le.msg == re.msg - default: - return false - } - } -} - -public struct ParserRuntime { public init() {} - public func run(program: P4.Parser, input: P4.Packet) -> Result { - - // First, find the start state. - guard var start_state = program.findStartState() else { - return Result.Error(Error(withMessage: "Could not find the start state")) - } - var execution = P4.ParserExecution(start_state) - while execution.state != P4.accept && execution.state != P4.reject { - execution = execution.state.evaluate(execution: execution) - } - return Result.Ok + public var description: String { + return "Runtime:\nScopes: \(scopes)" + } +} + +//public struct ParserRuntime: ProgramRuntime { +public class ParserRuntime: CustomStringConvertible { + var execution: ParserExecution + + init(execution: ParserExecution) { + self.execution = execution + } + + public static func create(program: P4.Parser) -> Result { + // First, find the start state. + guard let start_state = program.findStartState() else { + return Result.Error(Error(withMessage: "Could not find the start state")) + } + return Result.Ok(P4.ParserRuntime(execution: P4.ParserExecution(start_state))) + } + + public func run(input: P4.Packet) -> Result { + execution.scopes.enter() + print("Execution: \(execution)") + while execution.state != P4.accept && execution.state != P4.reject { + execution = execution.state.evaluate(execution: execution) + print("Execution: \(execution)") + } + return .Ok(Nothing()) + } + + public var description: String { + //return "\(super.description)\nState: \(execution?.description ?? "N/A")\nError: \(error?.description ?? "None")" + return "Runtime:\nExecution: \(execution)" } } diff --git a/Sources/P4/Support.swift b/Sources/P4/Support.swift new file mode 100644 index 0000000..ac3b45a --- /dev/null +++ b/Sources/P4/Support.swift @@ -0,0 +1,55 @@ +public struct Error: Equatable { + public private(set) var msg: String + + public init(withMessage msg: String) { + self.msg = msg + } +} + +public struct Nothing: CustomStringConvertible { + public var description: String { + return "Nothing" + } + + public init() {} +} + + +public enum Result: Equatable, CustomStringConvertible { + case Ok(T) + case Error(Error) + + public static func == (lhs: Result, rhs: Result) -> Bool { + switch (lhs, rhs) { + case (Ok, Ok): + return true + case (Error(let le), Error(let re)): + return le.msg == re.msg + default: + return false + } + } + + public func error() -> Error? { + if case Result.Error(let e) = self { + return e + } + return nil + } + + public var description: String { + switch self { + case Result.Error(let e): + return e.msg + case Result.Ok(let o): + return "\(o)" + } + } +} + +@freestanding(expression) public macro RequireOkResult(_: Result) -> Bool = + #externalMacro(module: "P4Macros", type: "RequireResult") +@freestanding(expression) public macro RequireErrorResult(_: Error, _: Result) -> Bool = + #externalMacro(module: "P4Macros", type: "RequireErrorResult") +@freestanding(expression) public macro UseOkResult(_: Result) -> T = + #externalMacro(module: "P4Macros", type: "UseOkResult") diff --git a/Sources/P4/Types.swift b/Sources/P4/Types.swift index 088e360..61e7bee 100644 --- a/Sources/P4/Types.swift +++ b/Sources/P4/Types.swift @@ -1,12 +1,26 @@ // The Swift Programming Language // https://docs.swift.org/swift-book -public enum ValueType { +public enum ValueType: CustomStringConvertible { case Boolean(Bool) + + public var description: String { + switch self { + case ValueType.Boolean(let b): + return "\(b) of Boolean" + } + } } -public struct Value { +public struct Value: CustomStringConvertible { public var value_type: ValueType + + public init(withValue value: ValueType) { + self.value_type = value + } + public var description: String { + return "\(value_type)" + } } public class Packet { diff --git a/Sources/P4Macros/Macros.swift b/Sources/P4Macros/Macros.swift new file mode 100644 index 0000000..d63c7cd --- /dev/null +++ b/Sources/P4Macros/Macros.swift @@ -0,0 +1,85 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxMacros + +public struct UseOkResult: ExpressionMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> ExprSyntax { + + guard let argument = node.argumentList.first?.expression else { + throw Require.Error.SyntaxError + } + + return """ + { + if case Result.Ok(let __runtime) = \(argument) { + return __runtime + } else { + print("Oh no") + throw Require.Error.UnexpectedResult + } + }() + """ + } +} + +public struct Require { + public enum Error: Swift.Error { + case UnexpectedResult + case SyntaxError + } +} + +public struct RequireResult: ExpressionMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> ExprSyntax { + + guard let argument = node.argumentList.first?.expression else { + throw Require.Error.SyntaxError + } + + return """ + { + if case Result.Ok(_) = \(argument) { + true + } else { + false + } + }() + """ + } +} + +public struct RequireErrorResult: ExpressionMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> ExprSyntax { + + let arguments = node.argumentList.indices + let expected_error = node.argumentList[arguments.startIndex] + let error_producer = node.argumentList[arguments.index(after: arguments.startIndex)] + + return """ + { + if case Result.Error(\(expected_error)) = \(error_producer) { + true + } else { + false + } + }() + """ + } +} + + +@main +struct P4Macros: CompilerPlugin { + var providingMacros: [Macro.Type] = [ + RequireResult.self, RequireErrorResult.self, UseOkResult.self, + ] +} diff --git a/Sources/Parser/Parser.swift b/Sources/Parser/Parser.swift index 69d2df8..a4a9c57 100644 --- a/Sources/Parser/Parser.swift +++ b/Sources/Parser/Parser.swift @@ -7,7 +7,6 @@ import TreeSitterP4 extension MutableTree { public func isError(lang: Language) -> Bool { - // TODO: Make a function. guard let parser_error_query = try? SwiftTreeSitter.Query( language: lang, @@ -22,7 +21,6 @@ extension MutableTree { for _ in error_qr { return true } - return false } } @@ -30,40 +28,129 @@ extension MutableTree { let p4lang = Language(tree_sitter_p4()) public protocol ParseableParserStatement { - static func Parse(node: Node, inTree tree: MutableTree) -> P4.ParserStatement? + static func Parse(node: Node, inTree tree: MutableTree) -> Result } -extension P4.ExpressionStatement : ParseableParserStatement{ - public static func Parse(node: Node, inTree tree: MutableTree) -> P4.ParserStatement? { - return P4.ExpressionStatement() +extension P4.ExpressionStatement: ParseableParserStatement { + public static func Parse(node: Node, inTree tree: MutableTree) -> Result { + guard + let parser_state_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(expressionStatement (expression) @expression)" + ).data(using: String.Encoding.utf8)!) + else { + return Result.Ok(.none) + } + + let qr = parser_state_query.execute(node: node, in: tree) + let query_result = qr.next()! + let expression_capture = query_result.captures(named: "expression") + if !expression_capture.isEmpty { + // TODO: Actually create an ExpressionStatement + return Result.Ok(P4.ExpressionStatement()) + } + + return Result.Ok(.none) + + } +} + +extension P4.VariableDeclarationStatement: ParseableParserStatement { + public static func Parse(node: Node, inTree tree: MutableTree) -> Result { + guard + let parser_state_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "((annotations)? (typeRef) @type-name variable_name: (identifier) @identifier)" + ).data(using: String.Encoding.utf8)!) + else { + return Result.Ok(.none) + } + + let qr = parser_state_query.execute(node: node, in: tree) + let parser_declaration = qr.next()! + + let type_name_capture = parser_declaration.captures(named: "type-name") + let variable_name_capture = parser_declaration.captures(named: "identifier") + + // There must be a state name and there must be a transition statement. + guard !type_name_capture.isEmpty, + !variable_name_capture.isEmpty, + let variable_name = variable_name_capture[0].node.text + else { + return Result.Error(Error(withMessage: "Could not parse a parser declaration")) + } + + return Result.Ok( + // TODO: Add support for parsing the value. + P4.VariableDeclarationStatement( + withIdentifier: Identifier( + name: variable_name, withValue: Value(withValue: ValueType.Boolean(true))))) } } public struct Parser { - static func ParserStatements(capture: [QueryCapture], inTree tree: MutableTree) -> [P4.ParserStatement]? { - var statements: [P4.ParserStatement] = Array() + static func ParserLocalElements(capture: [QueryCapture], inTree tree: MutableTree) -> Result< + [P4.ParserStatement] + > { + let localElementsParsers: [ParseableParserStatement.Type] = [ + P4.VariableDeclarationStatement.self + ] - let statementParsers = [P4.ExpressionStatement.self] + var localElements: [P4.ParserStatement] = Array() - for raw_statement in capture { - var parsed_statement: Optional = .none + for raw_le_statement in capture { + var parsed_le_statement: P4.ParserStatement? = .none - // Iterate through statement parsers and give each one a chance. - for parser in statementParsers { - if let parsed = parser.Parse(node: raw_statement.node, inTree: tree) { - parsed_statement = parsed - } + for le_parser in localElementsParsers { + if case Result.Ok(.some(let parsed)) = le_parser.Parse(node: raw_le_statement.node, inTree: tree) { + parsed_le_statement = parsed + break } + } - if let statement = parsed_statement { - statements.append(statement) - } else { - // There were no parseable statements. - return nil - } + if let le_statement = parsed_le_statement { + localElements.append(le_statement) + } else { + // There were no parseable statements. + return Result.Error( + Error(withMessage: "Failed to parse a local element: \(raw_le_statement)")) + } } - return statements + return Result.Ok(localElements) + } + + static func ParserStatements(capture: [QueryCapture], inTree tree: MutableTree) -> Result< + [P4.ParserStatement] + > { + let statementParsers: [ParseableParserStatement.Type] = [ + P4.ExpressionStatement.self, P4.VariableDeclarationStatement.self, + ] + + var statements: [P4.ParserStatement] = Array() + + for raw_statement in capture { + var parsed_statement: P4.ParserStatement? = .none + + // Iterate through statement parsers and give each one a chance. + for parser in statementParsers { + if case Result.Ok(.some(let parsed)) = parser.Parse(node: raw_statement.node, inTree: tree) { + parsed_statement = parsed + break + } + } + + if let statement = parsed_statement { + statements.append(statement) + } else { + // There were no parseable statements. + return Result.Error( + Error(withMessage: "Failed to parse a statement element: \(raw_statement)")) + } + } + return Result.Ok(statements) } static func ParserTransitionStatement(node: Node, inTree tree: MutableTree) -> P4 @@ -72,51 +159,67 @@ public struct Parser { return P4.ParserTransitionStatement() } - static func ParserState(node: Node, inTree tree: MutableTree) -> P4.ParserState? { + static func ParserState(node: Node, inTree tree: MutableTree) -> Result { guard let parser_state_query = try? SwiftTreeSitter.Query( language: p4lang, data: String( - "(parserState (state) (identifier) @state-name (parserStatements)? @state-statements (parserTransitionStatement) @transition)" + "(parserState (state) (identifier) @state-name (parserLocalElements)? @state-local-elements (parserStatements)? @state-statements (parserTransitionStatement) @transition)" ).data(using: String.Encoding.utf8)!) else { - return nil + return Result.Error(Error(withMessage: "Could not compile the tree sitter query")) } let qr = parser_state_query.execute(node: node, in: tree) - // TODO: Assert that there is only one value here. - for parser_declaration in qr { + let parser_declaration = qr.next()! + let transition_capture = parser_declaration.captures(named: "transition") + let state_name_capture = parser_declaration.captures(named: "state-name") + let state_le_capture = parser_declaration.captures(named: "state-local-elements") + let statements_capture = parser_declaration.captures(named: "state-statements") - let transition_capture = parser_declaration.captures( - named: "transition") - let state_name_capture = parser_declaration.captures(named: "state-name") - let statements_capture = parser_declaration.captures(named: "state-statements") - - guard !state_name_capture.isEmpty, - !transition_capture.isEmpty, - let parsed_state_name = state_name_capture[0].node.text, - let transition_statement = ParserTransitionStatement( - node: transition_capture[0].node, inTree: tree) - else { - return nil - } - - let parsed_statements = if !statements_capture.isEmpty { - ParserStatements(capture: statements_capture, inTree: tree) - } else { - Optional<[P4.ParserStatement]>.none - } - - // TODO: Validate that there is only one! - return P4.ParserState(name: parsed_state_name, withStatements: parsed_statements, withTransition: transition_statement) + // There must be a state name and there must be a transition statement. + guard !state_name_capture.isEmpty, + !transition_capture.isEmpty, + let parsed_state_name = state_name_capture[0].node.text, + let transition_statement = ParserTransitionStatement( + node: transition_capture[0].node, inTree: tree) + else { + return Result.Error(Error(withMessage: "Could not parse a parser declaration")) } - return nil + let maybe_parsed_les = + if !state_le_capture.isEmpty { + ParserLocalElements(capture: state_le_capture, inTree: tree) + } else { + Result.Ok([P4.ParserStatement]()) + } + + guard case Result<[P4.ParserStatement]>.Ok(let parsed_les) = maybe_parsed_les else { + return Result.Error(maybe_parsed_les.error()!) + } + + let maybe_parsed_statements = + if !statements_capture.isEmpty { + ParserStatements(capture: statements_capture, inTree: tree) + } else { + Result.Ok([P4.ParserStatement]()) + } + guard case Result<[P4.ParserStatement]>.Ok(let parsed_statements) = maybe_parsed_statements + else { + return Result.Error(maybe_parsed_statements.error()!) + } + + // TODO: Validate that there is only one! + return Result.Ok( + P4.ParserState( + name: parsed_state_name, withLocalElements: parsed_les, + withStatements: parsed_statements, + withTransition: transition_statement)) } - static func Parser(node: Node, inTree tree: MutableTree) -> P4.Parser? { + static func Parser(node: Node, inTree tree: MutableTree) -> Result { guard let parser_state_query = try? SwiftTreeSitter.Query( language: p4lang, @@ -124,42 +227,39 @@ public struct Parser { "(parserStates) @parser-states" ).data(using: String.Encoding.utf8)!) else { - return nil + return Result.Error( + Error(withMessage: "Could not compile the parser state tree sitter query")) } var parser = P4.Parser() - // Build a state from each one listed. for parser_states in parser_state_query.execute(node: node, in: tree) { - if let state = ParserState(node: parser_states.nodes[0], inTree: tree) { - parser.states.append(state) + switch ParserState(node: parser_states.nodes[0], inTree: tree) { + case Result.Ok(let state): parser.states.append(state) + case Result.Error(let error): return Result.Error(error) } } - - return parser + return Result.Ok(parser) } - public static func Program(_ source: String) -> P4.Program? { + public static func Program(_ source: String) -> Result { let p = SwiftTreeSitter.Parser.init() do { try p.setLanguage(p4lang) } catch { - return nil + return Result.Error(Error(withMessage: "Could not configure the P4 parser")) } - // Parse and check whether it is valid. let result = p.parse(source) guard let tree = result, !tree.isError(lang: p4lang) else { - - return nil + return Result.Error(Error(withMessage: "Could not compile the P4 program")) } - // Query for the parser declarations. guard let parser_declaration_query = try? SwiftTreeSitter.Query( language: p4lang, @@ -167,7 +267,8 @@ public struct Parser { "(parserDeclaration (parserType) (parserStates) @parser-states)" ).data(using: String.Encoding.utf8)!) else { - return nil + return Result.Error( + Error(withMessage: "Could not compile the parser declaration tree sitter query")) } var program: P4.Program = P4.Program() @@ -175,13 +276,12 @@ public struct Parser { let parser_qc = parser_declaration_query.execute(in: tree) for parser_declaration in parser_qc { - if let parser = Parser( - node: parser_declaration.nodes[0], inTree: tree) - { - program.parsers.append(parser) + switch Parser(node: parser_declaration.nodes[0], inTree: tree) { + case Result.Ok(let parser): program.parsers.append(parser) + case Result.Error(let error): return Result.Error(error) } } - return program + return Result.Ok(program) } } diff --git a/Tests/p4lmTests/ParserTests.swift b/Tests/p4lmTests/ParserTests.swift index f7032ee..220c329 100644 --- a/Tests/p4lmTests/ParserTests.swift +++ b/Tests/p4lmTests/ParserTests.swift @@ -5,6 +5,8 @@ import TreeSitterP4 import Foundation import P4 +import P4Macros + @testable import Parser @Test func test_simple_parser() async throws { @@ -16,7 +18,7 @@ import P4 } """ - let program = try #require(Parser.Program(simple_parser_declaration)) + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) #expect(program.parsers.count == 1) #expect(program.parsers[0].states.count == 1) @@ -32,7 +34,7 @@ import P4 } } """ - #expect(Parser.Program(simple_parser_declaration) == nil) + #expect(#RequireErrorResult(Error(withMessage: "Could not compile the P4 program"), Parser.Program(simple_parser_declaration))) } @Test func test_simple_parser_with_statement() async throws { @@ -45,7 +47,7 @@ import P4 } """ - let program = try #require(Parser.Program(simple_parser_declaration)) + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) #expect(program.parsers.count == 1) #expect(program.parsers[0].states.count == 1) diff --git a/Tests/p4lmTests/RuntimeTests.swift b/Tests/p4lmTests/RuntimeTests.swift index ba5232c..9135752 100644 --- a/Tests/p4lmTests/RuntimeTests.swift +++ b/Tests/p4lmTests/RuntimeTests.swift @@ -1,5 +1,6 @@ import Foundation import P4 +import P4Macros import SwiftTreeSitter import Testing import TreeSitter @@ -17,11 +18,8 @@ import TreeSitterP4 } """ - let program = try #require(Parser.Program(simple_parser_declaration)) - - let runtime = P4.ParserRuntime() - - #expect(runtime.run(program: program.parsers[0], input: P4.Packet()) == P4.Result.Ok) + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + #expect(#RequireOkResult(P4.ParserRuntime.create(program: program.parsers[0]))) } @Test func test_simple_runtime_no_start_state() async throws { @@ -34,10 +32,34 @@ import TreeSitterP4 } """ - let program = try #require(Parser.Program(simple_parser_declaration)) - + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) #expect( - P4.ParserRuntime().run(program: program.parsers[0], input: P4.Packet()) - == Result.Error(Error(withMessage: "Could not find the start state"))) - + #RequireErrorResult( + Error(withMessage: "Could not find the start state"), + P4.ParserRuntime.create(program: program.parsers[0]))) +} + +@Test func test_simple_runtime_output() async throws { + let simple_parser_declaration = """ + parser simple() { + state start { + bool b = true; + transition reject; + } + } + """ + + /* + TODO: Add tests for "semantic" parsing failures. Here's an example! + print(Parser.Program(simple_parser_declaration)) + #expect( + #RequireErrorResult( + Error( + withMessage: + "Failed to parse a local element: >" + ), Parser.Program(simple_parser_declaration))) + */ + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + let runtime = try #UseOkResult(P4.ParserRuntime.create(program: program.parsers[0])) + #expect(runtime.run(input: P4.Packet()) == P4.Result.Ok(Nothing())) }