From 4bec71dcf4c3ee0c02bc36f5caeead600853839f Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Tue, 20 Jan 2026 07:10:58 -0500 Subject: [PATCH] Refactor Library And Start Runtime Signed-off-by: Will Hawkins --- Package.swift | 21 +++- Sources/P4/Parser.swift | 113 +++++++++++++++++ Sources/P4/Program.swift | 4 + Sources/P4/Runtime.swift | 40 ++++++ Sources/P4/Types.swift | 14 +++ Sources/Parser/Execution.swift | 0 Sources/Parser/Parser.swift | 187 +++++++++++++++++++++++++++++ Sources/p4lm/p4lm.swift | 25 ---- Tests/p4lmTests/ParserTests.swift | 55 +++++++++ Tests/p4lmTests/RuntimeTests.swift | 43 +++++++ Tests/p4lmTests/p4lmTests.swift | 35 ------ 11 files changed, 472 insertions(+), 65 deletions(-) create mode 100644 Sources/P4/Parser.swift create mode 100644 Sources/P4/Program.swift create mode 100644 Sources/P4/Runtime.swift create mode 100644 Sources/P4/Types.swift create mode 100644 Sources/Parser/Execution.swift create mode 100644 Sources/Parser/Parser.swift delete mode 100644 Sources/p4lm/p4lm.swift create mode 100644 Tests/p4lmTests/ParserTests.swift create mode 100644 Tests/p4lmTests/RuntimeTests.swift delete mode 100644 Tests/p4lmTests/p4lmTests.swift diff --git a/Package.swift b/Package.swift index d215cf4..fb7c551 100644 --- a/Package.swift +++ b/Package.swift @@ -8,28 +8,39 @@ let package = Package( products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( - name: "p4lm", - targets: ["p4lm"] + name: "Parser", + targets: ["Parser"] + ), + .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"), ], targets: [ // 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: "p4lm", + name: "Parser", dependencies: [ .product(name: "SwiftTreeSitter", package: "swift-tree-sitter"), .product(name: "SwiftTreeSitterLayer", package: "swift-tree-sitter"), .product(name: "TreeSitterP4", package: "tree-sitter-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", + ), .testTarget( - name: "p4lmTests", - dependencies: ["p4lm"] + name: "ParserTests", + dependencies: ["Parser", "P4"] ), ] ) diff --git a/Sources/P4/Parser.swift b/Sources/P4/Parser.swift new file mode 100644 index 0000000..7dbe7a5 --- /dev/null +++ b/Sources/P4/Parser.swift @@ -0,0 +1,113 @@ +public struct LocalElements { + +} + +public struct LocalElement { + +} + +public struct ParserExecution { + public var state: ParserState + + public init(_ state: ParserState) { + self.state = state + } + + public func transition(toNextState state: ParserState) -> ParserExecution { + return ParserExecution(state) + } +} + +public protocol Expression { + /// Evaluate an expression for a given execution + /// - Parameters + /// - execution: The execution context in which to evaluate the expression + /// - Returns: The value of expression + func evaluate(execution: ParserExecution) -> Value +} + +public protocol ParserStatement: Sendable { + /// Evaluate a statement for a given execution + /// - Parameters + /// - execution: The execution context in which to evaluate the parser statement + /// - Returns: An updated execution after evaluating the parser statement + func evaluate(execution: ParserExecution) -> ParserExecution +} + +public struct ParserTransitionStatement: ParserStatement { + public init() {} + public func evaluate(execution: ParserExecution) -> ParserExecution { + return execution + } +} + +public struct ExpressionStatement: ParserStatement { + public init() {} + public func evaluate(execution: ParserExecution) -> ParserExecution { + return execution + } +} + +public struct ParserState: Equatable, Sendable { + public private(set) var state_name: String + public private(set) var statements: [ParserStatement] + public private(set) var transition: ParserTransitionStatement? + + 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) { + state_name = name + transition = transitionStatement + self.statements = statements ?? Array() + } + + func evaluate(execution: ParserExecution) -> ParserExecution { + var currentExecution = execution + for statement in statements { + currentExecution = statement.evaluate(execution: currentExecution) + } + + return if let transition = transition { + execution.transition(toNextState: accept) + } else { + execution.transition(toNextState: reject) + } + } + + /// (private) constructor (no transition) + /// + /// accept and reject are the only final states and they are constructed internally. + init(name: String) { + state_name = name + transition = .none + statements = Array() + } +} + +public struct ParserStates: Sendable { + public var states: [ParserState] = Array() +} + +public let accept: ParserState = ParserState(name: "accept") +public let reject: ParserState = ParserState(name: "reject") + +public struct Parser { + public var states: [ParserState] = Array() + public var count: Int { + states.count + } + + public init() {} + + public func findStartState() -> Optional { + for state in states { + if state.state_name == "start" { + return state + } + } + return .none + } +} diff --git a/Sources/P4/Program.swift b/Sources/P4/Program.swift new file mode 100644 index 0000000..f933fe5 --- /dev/null +++ b/Sources/P4/Program.swift @@ -0,0 +1,4 @@ +public struct Program { + public var parsers: [P4.Parser] = Array() + public init() { } +} \ No newline at end of file diff --git a/Sources/P4/Runtime.swift b/Sources/P4/Runtime.swift new file mode 100644 index 0000000..3f41101 --- /dev/null +++ b/Sources/P4/Runtime.swift @@ -0,0 +1,40 @@ +public struct Error { + public private(set) var msg: String + + 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 + } +} diff --git a/Sources/P4/Types.swift b/Sources/P4/Types.swift new file mode 100644 index 0000000..088e360 --- /dev/null +++ b/Sources/P4/Types.swift @@ -0,0 +1,14 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book + +public enum ValueType { + case Boolean(Bool) +} + +public struct Value { + public var value_type: ValueType +} + +public class Packet { + public init() {} +} \ No newline at end of file diff --git a/Sources/Parser/Execution.swift b/Sources/Parser/Execution.swift new file mode 100644 index 0000000..e69de29 diff --git a/Sources/Parser/Parser.swift b/Sources/Parser/Parser.swift new file mode 100644 index 0000000..69d2df8 --- /dev/null +++ b/Sources/Parser/Parser.swift @@ -0,0 +1,187 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book + +import P4 +import SwiftTreeSitter +import TreeSitterP4 + +extension MutableTree { + public func isError(lang: Language) -> Bool { + // TODO: Make a function. + guard + let parser_error_query = try? SwiftTreeSitter.Query( + language: lang, + data: String( + "(ERROR)" + ).data(using: String.Encoding.utf8)!) + else { + return false + } + + let error_qr = parser_error_query.execute(in: self) + for _ in error_qr { + return true + } + + return false + } +} + +let p4lang = Language(tree_sitter_p4()) + +public protocol ParseableParserStatement { + static func Parse(node: Node, inTree tree: MutableTree) -> P4.ParserStatement? +} + +extension P4.ExpressionStatement : ParseableParserStatement{ + public static func Parse(node: Node, inTree tree: MutableTree) -> P4.ParserStatement? { + return P4.ExpressionStatement() + } +} + +public struct Parser { + static func ParserStatements(capture: [QueryCapture], inTree tree: MutableTree) -> [P4.ParserStatement]? { + var statements: [P4.ParserStatement] = Array() + + let statementParsers = [P4.ExpressionStatement.self] + + for raw_statement in capture { + var parsed_statement: Optional = .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 + } + } + + if let statement = parsed_statement { + statements.append(statement) + } else { + // There were no parseable statements. + return nil + } + } + + return statements + } + + static func ParserTransitionStatement(node: Node, inTree tree: MutableTree) -> P4 + .ParserTransitionStatement? + { + return P4.ParserTransitionStatement() + } + + static func ParserState(node: Node, inTree tree: MutableTree) -> P4.ParserState? { + guard + let parser_state_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(parserState (state) (identifier) @state-name (parserStatements)? @state-statements (parserTransitionStatement) @transition)" + ).data(using: String.Encoding.utf8)!) + else { + return nil + } + + 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 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) + } + + return nil + } + + static func Parser(node: Node, inTree tree: MutableTree) -> P4.Parser? { + guard + let parser_state_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(parserStates) @parser-states" + ).data(using: String.Encoding.utf8)!) + else { + return nil + } + + 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) + } + } + + return parser + } + + public static func Program(_ source: String) -> P4.Program? { + + let p = SwiftTreeSitter.Parser.init() + + do { + try p.setLanguage(p4lang) + } catch { + return nil + } + + // Parse and check whether it is valid. + let result = p.parse(source) + guard let tree = result, + !tree.isError(lang: p4lang) + else { + + return nil + } + + // Query for the parser declarations. + guard + let parser_declaration_query = try? SwiftTreeSitter.Query( + language: p4lang, + data: String( + "(parserDeclaration (parserType) (parserStates) @parser-states)" + ).data(using: String.Encoding.utf8)!) + else { + return nil + } + + var program: P4.Program = P4.Program() + + 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) + } + } + + return program + } +} diff --git a/Sources/p4lm/p4lm.swift b/Sources/p4lm/p4lm.swift deleted file mode 100644 index 18e8e36..0000000 --- a/Sources/p4lm/p4lm.swift +++ /dev/null @@ -1,25 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book - -import SwiftTreeSitter -import TreeSitterP4 - -func parse(_ source: String) -> Optional { - - let p4lang = Language(tree_sitter_p4()) - - let p = Parser.init() - - do { - try p.setLanguage(p4lang) - } catch { - return .none - } - - let result = p.parse(source) - - guard let tree = result else { - return .none - } - return tree -} diff --git a/Tests/p4lmTests/ParserTests.swift b/Tests/p4lmTests/ParserTests.swift new file mode 100644 index 0000000..f7032ee --- /dev/null +++ b/Tests/p4lmTests/ParserTests.swift @@ -0,0 +1,55 @@ +import Testing +import TreeSitter +import SwiftTreeSitter +import TreeSitterP4 +import Foundation +import P4 + +@testable import Parser + +@Test func test_simple_parser() async throws { + let simple_parser_declaration = """ + parser simple() { + state start { + transition drop; + } + } + """ + + let program = try #require(Parser.Program(simple_parser_declaration)) + + #expect(program.parsers.count == 1) + #expect(program.parsers[0].states.count == 1) + #expect(program.parsers[0].states[0].state_name == "start") + #expect(program.parsers[0].states[0].statements.count == 0) +} + +@Test func test_simple_parser_syntax_error() async throws { + let simple_parser_declaration = """ + parser simple() { + state + transition drop; + } + } + """ + #expect(Parser.Program(simple_parser_declaration) == nil) +} + +@Test func test_simple_parser_with_statement() async throws { + let simple_parser_declaration = """ + parser simple() { + state start { + true; + transition drop; + } + } + """ + + let program = try #require(Parser.Program(simple_parser_declaration)) + + #expect(program.parsers.count == 1) + #expect(program.parsers[0].states.count == 1) + #expect(program.parsers[0].states[0].state_name == "start") + #expect(program.parsers[0].states[0].statements.count == 1) +} + diff --git a/Tests/p4lmTests/RuntimeTests.swift b/Tests/p4lmTests/RuntimeTests.swift new file mode 100644 index 0000000..ba5232c --- /dev/null +++ b/Tests/p4lmTests/RuntimeTests.swift @@ -0,0 +1,43 @@ +import Foundation +import P4 +import SwiftTreeSitter +import Testing +import TreeSitter +import TreeSitterP4 + +@testable import Parser + +@Test func test_simple_runtime() async throws { + let simple_parser_declaration = """ + parser simple() { + state start { + true; + transition reject; + } + } + """ + + 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) +} + +@Test func test_simple_runtime_no_start_state() async throws { + let simple_parser_declaration = """ + parser simple() { + state tart { + true; + transition reject; + } + } + """ + + let program = try #require(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"))) + +} diff --git a/Tests/p4lmTests/p4lmTests.swift b/Tests/p4lmTests/p4lmTests.swift deleted file mode 100644 index cc19fbf..0000000 --- a/Tests/p4lmTests/p4lmTests.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Testing -import TreeSitter -import SwiftTreeSitter -import TreeSitterP4 -import Foundation - -@testable import p4lm - -@Test func example() async throws { - let simple_parser_declaration = """ - parser simple() { - state testing {} - } - """ - - guard let tree = p4lm.parse(simple_parser_declaration) else { - assert(false, "Could not parse the simple parser declaration.") - } - - let p4lang = Language(tree_sitter_p4()) - let query = try! SwiftTreeSitter.Query(language: p4lang, data: String("(parserDeclaration (parserTypeDeclaration (parser) parser_name: (identifier) @parser-name))").data(using: String.Encoding.utf8)!) - - let qr = query.execute(in: tree) - - // TODO: Figure out how to actually determine the number of matches. - - guard let parser_declaration = qr.next() else { - assert(false, "Could not parse the simple parser declaration (No parser declaration).") - } - - let parser_name_capture = parser_declaration.captures(named: "parser-name")[0] - - #expect(parser_name_capture.node.text == Optional.some("simple")) - -}