From c3fdfb62e8723193638fe14df1406067571ecfa6 Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Fri, 6 Feb 2026 07:46:18 -0500 Subject: [PATCH] Refactor Type System Signed-off-by: Will Hawkins --- README.md | 19 +- Sources/Common/ProgramTypes.swift | 206 +++++++++++++----- Sources/Common/Protocols.swift | 10 +- Sources/Lang/Instantiation.swift | 22 ++ Sources/Lang/Parser.swift | 20 +- Sources/Lang/Program.swift | 25 ++- Sources/Parser/Parser.swift | 85 ++++---- Sources/Runtime/Runtime.swift | 24 +- Sources/TreeSitterExtensions/Extensions.swift | 18 +- Tests/p4lmTests/ParserTests.swift | 47 ++-- Tests/p4lmTests/RuntimeTests.swift | 118 +++++----- 11 files changed, 395 insertions(+), 199 deletions(-) create mode 100644 Sources/Lang/Instantiation.swift diff --git a/README.md b/README.md index 07dd2c9..ba862e3 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,21 @@ Very, very alpha: 1. Limited parts of the language can be parsed. 2. Limited programs can be evaluated. -Please check back often! \ No newline at end of file +Please check back often! + +### Building + +#### Generating Documentation + +To build the documentation: + +```console +$ swift package generate-documentation +``` + +To preview the generated documentation: +```console +$ swift package swift package --disable-sandbox preview-documentation --target +``` + +For more information, see the [documentation for the Swift-DocC plugin](https://swiftlang.github.io/swift-docc-plugin/documentation/swiftdoccplugin/). \ No newline at end of file diff --git a/Sources/Common/ProgramTypes.swift b/Sources/Common/ProgramTypes.swift index d7454d1..ba8e0e5 100644 --- a/Sources/Common/ProgramTypes.swift +++ b/Sources/Common/ProgramTypes.swift @@ -15,85 +15,171 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . - +/// A P4 identifier public class Identifier: CustomStringConvertible, Equatable { - var name: String + var name: String - public init(name: String) { - self.name = name - } + public init(name: String) { + self.name = name + } - public var description: String { - return "\(name)" - } + public var description: String { + return "\(name)" + } - public static func ==(lhs: Identifier, rhs: Identifier) -> Bool { - return lhs.name == rhs.name - } + public static func == (lhs: Identifier, rhs: Identifier) -> Bool { + return lhs.name == rhs.name + } } +/// A P4 variable public class Variable: Identifier { - var constant: Bool - var value: ValueType + var constant: Bool + var value: P4Value - public init(name: String, withValue value: ValueType, isConstant constant: Bool) { - self.constant = constant - self.value = value - super.init(name: name) - } + public init(name: String, withValue value: P4Value, isConstant constant: Bool) { + self.constant = constant + self.value = value + super.init(name: name) + } - public override var description: String { - return "\(super.description) = \(value) \(constant ? "(constant)" : "")" - } + public override var description: String { + return "\(super.description) = \(value) \(constant ? "(constant)" : "")" + } - public var value_type: ValueType { - get { - value - } - } + public var value_type: P4Value { + value + } } -public enum ValueType: CustomStringConvertible, Equatable { - case Boolean(Bool) - case Int(Int) - case String(String) +/// A base for all instances of P4 types +open class P4ValueBase: P4Value { - public var description: String { - switch self { - case ValueType.Boolean(let b): - return "\(b) of Boolean" - case ValueType.Int(let i): - return "\(i) of Int" - case ValueType.String(let s): - return "\(s) of String" - } - } - - public static func==(lhs: ValueType, rhs: ValueType) -> Bool { - switch (lhs,rhs) { - case (ValueType.Boolean(let lhsb), ValueType.Boolean(let rhsb)): - return lhsb == rhsb - case (ValueType.String(let lhsb), ValueType.String(let rhsb)): - return lhsb == rhsb - case (ValueType.Int(let lhsb), ValueType.Int(let rhsb)): - return lhsb == rhsb - default: return false - } - } + public init() {} + public func type() -> P4Type { + return T.create() + } + public func eq(rhs: P4Value) -> Bool { + return false + } } -public struct Value: CustomStringConvertible { - public var value_type: ValueType +/// The type for a P4 struct +public struct P4Struct: P4Type { + public let name: String + // The type of the struct created is always anonymous. + public static func create() -> any P4Type { + return P4Struct() + } - public init(withValue value: ValueType) { - self.value_type = value + public init(withName name: String) { + self.name = name + } + public init() { + self.name = "" + } +} + +/// The field of a P4 struct +public struct P4StructField { + public let name: Identifier + public let type: P4Type + + public init(withName name: Identifier, withType type: P4Type) { + self.name = name + self.type = type + } +} + +/// An instance of a P4 struct +public class P4StructValue: P4ValueBase { + public let fields: [P4StructField] + public init(withFields fields: [P4StructField]) { + self.fields = fields + } +} + +/// A P4 boolean type +public struct P4Boolean: P4Type { + public static func create() -> any P4Type { + return P4Boolean() + } +} + +/// An instance of a P4 boolean +public class P4BooleanValue: P4ValueBase { + let value: Bool + + public init(withValue value: Bool) { + self.value = value + } + public override func eq(rhs: P4Value) -> Bool { + guard let bool_rhs = rhs as? P4BooleanValue else { + return false } - public var description: String { - return "\(value_type)" + return self.value == bool_rhs.value + } +} + +/// A P4 int type +public struct P4Int: P4Type { + public static func create() -> any P4Type { + return P4Int() + } +} + +/// An instance of a P4 integer +public class P4IntValue: P4ValueBase { + let value: Int + public init(withValue value: Int) { + self.value = value + } + public override func eq(rhs: P4Value) -> Bool { + guard let int_rhs = rhs as? P4IntValue else { + return false } + return self.value == int_rhs.value + } +} + +/// A P4 string type +public struct P4String: P4Type { + public static func create() -> any P4Type { + return P4String() + } +} +/// An instance of a P4 string +public class P4StringValue: P4ValueBase { + let value: String + public init(withValue value: String) { + self.value = value + } + public override func eq(rhs: P4Value) -> Bool { + guard let string_rhs = rhs as? P4StringValue else { + return false + } + return self.value == string_rhs.value + } +} + +/// A P4 value (with a type) +public struct Value: CustomStringConvertible, Equatable { + public var type: P4Type + public var value: P4Value + + public init(withValue value: P4Value, andType type: P4Type) { + self.value = value + self.type = type + } + public var description: String { + return "\(self.value) of \(self.type)" + } + public static func == (lhs: Value, rhs: Value) -> Bool { + return lhs.value.eq(rhs: rhs.value) + } } public class Packet { - public init() {} -} \ No newline at end of file + public init() {} +} diff --git a/Sources/Common/Protocols.swift b/Sources/Common/Protocols.swift index b9aceff..7f94285 100644 --- a/Sources/Common/Protocols.swift +++ b/Sources/Common/Protocols.swift @@ -20,7 +20,7 @@ public protocol EvaluatableExpression { /// - Parameters /// - execution: The execution context in which to evaluate the expression /// - Returns: The value of expression - func evaluate(execution: ProgramExecution) -> ValueType + func evaluate(execution: ProgramExecution) -> P4Value } public protocol EvaluatableParserStatement { @@ -31,3 +31,11 @@ public protocol EvaluatableParserStatement { func evaluate(execution: ProgramExecution) -> ProgramExecution } +public protocol P4Type { + static func create() -> P4Type +} + +public protocol P4Value { + func type() -> P4Type + func eq(rhs: P4Value) -> Bool +} diff --git a/Sources/Lang/Instantiation.swift b/Sources/Lang/Instantiation.swift new file mode 100644 index 0000000..2136251 --- /dev/null +++ b/Sources/Lang/Instantiation.swift @@ -0,0 +1,22 @@ +// 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 . + +import Common + +public struct Instantiation { + +} \ No newline at end of file diff --git a/Sources/Lang/Parser.swift b/Sources/Lang/Parser.swift index 6fb1909..6c31366 100644 --- a/Sources/Lang/Parser.swift +++ b/Sources/Lang/Parser.swift @@ -85,13 +85,21 @@ public struct ParserStates { nonisolated(unsafe) public let accept: ParserState = ParserState(name: "accept") nonisolated(unsafe) public let reject: ParserState = ParserState(name: "reject") -public struct Parser { +public struct Parser: P4Type { public var states: [ParserState] = Array() public var count: Int { states.count } - public init() {} + public var name: Identifier + + public init(withName name: Identifier) { + self.name = name + } + + public static func create() -> any P4Type { + return Parser(withName: Identifier(name: "")) + } public func findStartState() -> ParserState? { for state in states { @@ -103,25 +111,25 @@ public struct Parser { } } -public class ParserExecution: ProgramExecution { +public class ParserInstance: ProgramExecution { private init(state: ParserState) { self.state = state super.init() } - public static func create(_ parser: Parser) -> Result { + public static func create(_ parser: Parser) -> Result { guard let start_state = parser.findStartState() else { return Result.Error(Error(withMessage: "Could not find the start state")) } - let new = ParserExecution(state: start_state) + let new = ParserInstance(state: start_state) return Result.Ok(new) } public var state: ParserState - public func transition(toNextState state: ParserState) -> ParserExecution { + public func transition(toNextState state: ParserState) -> ParserInstance { let next = self next.state = state return next diff --git a/Sources/Lang/Program.swift b/Sources/Lang/Program.swift index 471c5bd..7025c4e 100644 --- a/Sources/Lang/Program.swift +++ b/Sources/Lang/Program.swift @@ -15,7 +15,30 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +import Common + public struct Program { - public var parsers: [Parser] = Array() + public var types: [P4Type] = Array() + + /// Find the program's main parser + /// + /// Note: For now, the main parser is expected to be named main_parser. + public func starting_parser() -> Result { + return self.find_parser(withName: Identifier(name: "main_parser")) + } + + public func find_parser(withName name: Identifier) -> Result { + for type in self.types { + print("type: \(type)") + guard let parser = type as? Parser else { + continue + } + if parser.name == name { + return .Ok(parser) + } + } + return .Error(Error(withMessage: "Could not find parser named \(name)")) + } + public init() {} } \ No newline at end of file diff --git a/Sources/Parser/Parser.swift b/Sources/Parser/Parser.swift index 7acf744..e15db62 100644 --- a/Sources/Parser/Parser.swift +++ b/Sources/Parser/Parser.swift @@ -24,45 +24,6 @@ import TreeSitterP4 let p4lang = Language(tree_sitter_p4()) -public protocol ParseableValueType { - static func Parse(type: String, withValue value: String) -> Result -} - -// This seems unnecessary because all the value types are in a single enum? -extension ValueType: ParseableValueType { - public static func Parse(type: String, withValue value: String) -> Result { - if type == "bool" { - // Default - if value == "" { - return .Ok(ValueType.Boolean(false)) - } - - if value == "true" { - return .Ok(ValueType.Boolean(true)) - } else if value == "false" { - return .Ok(ValueType.Boolean(false)) - } - return .Error(Error(withMessage: "Cannot convert \(value) into boolean value")) - - } else if type == "string" { - return .Ok(ValueType.String(value)) - - } else if type == "int" { - // Default - if value == "" { - return .Ok(ValueType.Int(0)) - } - - guard let parsed_value = Swift.Int(value) else { - return .Error(Error(withMessage: "Cannot convert \(value) into integer value")) - } - return .Ok(ValueType.Int(parsed_value)) - } - - return .Error(Error(withMessage: "Invalid type")) - } -} - public protocol ParseableParserStatement { static func Parse(node: Node, inTree tree: MutableTree) -> Result } @@ -129,7 +90,7 @@ extension VariableDeclarationStatement: ParseableParserStatement { "" } - return switch ValueType.Parse(type: type_name, withValue: value) { + return switch Parser.ParseValueType(type: type_name, withValue: value) { case Result.Ok(let value_type): Result.Ok( VariableDeclarationStatement( @@ -141,6 +102,37 @@ extension VariableDeclarationStatement: ParseableParserStatement { } public struct Parser { + static func ParseValueType(type: String, withValue value: String) -> Result { + if type == "bool" { + // Default + if value == "" { + return .Ok(P4BooleanValue(withValue: false)) + } + + if value == "true" { + return .Ok(P4BooleanValue(withValue: true)) + } else if value == "false" { + return .Ok(P4BooleanValue(withValue: false)) + } + return .Error(Error(withMessage: "Cannot convert \(value) into boolean value")) + + } else if type == "string" { + return .Ok(P4StringValue.init(withValue: value)) + + } else if type == "int" { + // Default + if value == "" { + return .Ok(P4IntValue.init(withValue: 0)) + } + + guard let parsed_value = Swift.Int(value) else { + return .Error(Error(withMessage: "Cannot convert \(value) into integer value")) + } + return .Ok(P4IntValue.init(withValue: parsed_value)) + } + + return .Error(Error(withMessage: "Invalid type")) + } public struct P4Parser { @@ -305,7 +297,7 @@ public struct Parser { withTransition: transition_statement)) } } - static func Parser(node: Node, inTree tree: MutableTree) -> Result { + static func Parser(withName name: Identifier, node: Node, inTree tree: MutableTree) -> Result { guard let parser_state_query = try? SwiftTreeSitter.Query( language: p4lang, @@ -317,7 +309,7 @@ public struct Parser { Error(withMessage: "Could not compile the parser state tree sitter query")) } - var parser = Lang.Parser() + var parser = Lang.Parser(withName: name) // Build a state from each one listed. for parser_states in parser_state_query.execute(node: node, in: tree) { @@ -340,7 +332,8 @@ public struct Parser { let result = p.parse(source) guard let tree = result, - !tree.isError(lang: p4lang) + !tree.isError(lang: p4lang), + !tree.containsMissing(lang: p4lang) else { return Result.Error(Error(withMessage: "Could not compile the P4 program")) } @@ -349,7 +342,7 @@ public struct Parser { let parser_declaration_query = try? SwiftTreeSitter.Query( language: p4lang, data: String( - "(parserDeclaration (parserType) (parserStates) @parser-states)" + "(parserDeclaration (parserType parser_name: (identifier) @parser-name) (parserStates) @parser-states)" ).data(using: String.Encoding.utf8)!) else { return Result.Error( @@ -361,8 +354,8 @@ public struct Parser { let parser_qc = parser_declaration_query.execute(in: tree) for parser_declaration in parser_qc { - switch Parser(node: parser_declaration.nodes[0], inTree: tree) { - case Result.Ok(let parser): program.parsers.append(parser) + switch Parser(withName: Identifier(name: parser_declaration.nodes[0].text!), node: parser_declaration.nodes[1], inTree: tree) { + case Result.Ok(let parser): program.types.append(parser) case Result.Error(let error): return Result.Error(error) } } diff --git a/Sources/Runtime/Runtime.swift b/Sources/Runtime/Runtime.swift index 0a769d1..70daa41 100644 --- a/Sources/Runtime/Runtime.swift +++ b/Sources/Runtime/Runtime.swift @@ -18,33 +18,39 @@ import Common import Lang +/// The runtime for a parser public class ParserRuntime: CustomStringConvertible { - var execution: ParserExecution + var execution: ParserInstance - init(execution: ParserExecution) { + init(execution: ParserInstance) { self.execution = execution } - public static func create(program: Lang.Parser) -> Result { - switch ParserExecution.create(program) { - case .Ok(let execution): return .Ok(Runtime.ParserRuntime(execution: execution)) - case .Error(let error): return .Error(error) - + /// Create a parser runtime from a P4 program + public static func create(program: Lang.Program) -> Result { + return switch program.starting_parser() { + case .Ok(let parser): + switch ParserInstance.create(parser) { + case .Ok(let execution): .Ok(Runtime.ParserRuntime(execution: execution)) + case .Error(let error): .Error(error) + } + case .Error(let error): .Error(error) } } + /// Run the P4 parser on a given packet public func run(input: Packet) -> Result<(ParserState, ProgramExecution)> { execution.scopes.enter() return .Ok(execution.execute()) } public var description: String { - //return "\(super.description)\nState: \(execution?.description ?? "N/A")\nError: \(error?.description ?? "None")" return "Runtime:\nExecution: \(execution)" } } -extension ParserExecution: Execution { +/// Instances of parsers are executable +extension ParserInstance: Execution { public func execute() -> (ParserState, ProgramExecution) { var execution = self as ProgramExecution var state = self.state diff --git a/Sources/TreeSitterExtensions/Extensions.swift b/Sources/TreeSitterExtensions/Extensions.swift index 643f22e..6adeb14 100644 --- a/Sources/TreeSitterExtensions/Extensions.swift +++ b/Sources/TreeSitterExtensions/Extensions.swift @@ -35,5 +35,21 @@ extension MutableTree { } return false } -} + public func containsMissing(lang: Language) -> Bool { + guard + let parser_error_query = try? SwiftTreeSitter.Query( + language: lang, + data: String( + "(MISSING)" + ).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 + } +} \ No newline at end of file diff --git a/Tests/p4lmTests/ParserTests.swift b/Tests/p4lmTests/ParserTests.swift index 9964525..9a2978d 100644 --- a/Tests/p4lmTests/ParserTests.swift +++ b/Tests/p4lmTests/ParserTests.swift @@ -29,47 +29,66 @@ import Macros @Test func test_simple_parser() async throws { let simple_parser_declaration = """ - parser simple() { + parser main_parser() { state start { transition drop; } - } + }; """ let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) - #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) + #expect(parser.states.count == 1) + #expect(parser.states[0].state_name == "start") + #expect(parser.states[0].statements.count == 0) } @Test func test_simple_parser_syntax_error() async throws { let simple_parser_declaration = """ - parser simple() { + parser main_parser() { state transition drop; } - } + }; """ #expect(#RequireErrorResult(Error(withMessage: "Could not compile the P4 program"), Parser.Program(simple_parser_declaration))) } @Test func test_simple_parser_with_statement() async throws { let simple_parser_declaration = """ - parser simple() { + parser main_parser() { state start { true; transition drop; } - } + }; + """ + + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + + #expect(parser.states.count == 1) + #expect(parser.states[0].state_name == "start") + #expect(parser.states[0].statements.count == 1) +} + +@Test func test_simple_parser_with_instantiation() async throws { + let simple_parser_declaration = """ + parser main_parser() { + state start { + true; + transition drop; + } + }; + bool() main; """ let program = try #UseOkResult(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) + let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + #expect(parser.states.count == 1) + #expect(parser.states[0].state_name == "start") + #expect(parser.states[0].statements.count == 1) } diff --git a/Tests/p4lmTests/RuntimeTests.swift b/Tests/p4lmTests/RuntimeTests.swift index d001cab..63577cd 100644 --- a/Tests/p4lmTests/RuntimeTests.swift +++ b/Tests/p4lmTests/RuntimeTests.swift @@ -15,11 +15,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -import Foundation -import Runtime import Common +import Foundation import Lang import Macros +import Runtime import SwiftTreeSitter import Testing import TreeSitter @@ -28,77 +28,75 @@ import TreeSitterP4 @testable import Parser @Test func test_simple_runtime() async throws { - let simple_parser_declaration = """ - parser simple() { - state start { - true; - transition reject; - } - } - """ + let simple_parser_declaration = """ + parser main_parser() { + state start { + true; + transition reject; + } + }; + """ - let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) - #expect(#RequireOkResult(Runtime.ParserRuntime.create(program: program.parsers[0]))) + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + #expect(#RequireOkResult(Runtime.ParserRuntime.create(program: program))) } @Test func test_simple_runtime_no_start_state() async throws { - let simple_parser_declaration = """ - parser simple() { - state tart { - true; - transition reject; - } - } - """ + let simple_parser_declaration = """ + parser main_parser() { + state tart { + true; + transition reject; + } + }; + """ - let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) - #expect( - #RequireErrorResult( - Error(withMessage: "Could not find the start state"), - Runtime.ParserRuntime.create(program: program.parsers[0]))) + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + #expect( + #RequireErrorResult( + Error(withMessage: "Could not find the start state"), + Runtime.ParserRuntime.create(program: program))) } @Test func test_simple_local_element_variable_declaration() async throws { - let simple_parser_declaration = """ - parser simple() { - state start { - bool b = false; - string s = "testing"; - true; - false; - transition reject; - } - } - """ + let simple_parser_declaration = """ + parser main_parser() { + state start { + bool b = false; + string s = "testing"; + true; + false; + transition reject; + } + }; + """ - let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) - let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program.parsers[0])) + let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) + let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) - // This seems awkward to me! - // TODO: Is there a better way? - guard case Common.Result.Ok(let (state_result, execution_result)) = runtime.run(input: Packet()) else { - assert(false) - } + // This seems awkward to me! + // TODO: Is there a better way? + guard case Common.Result.Ok(let (state_result, execution_result)) = runtime.run(input: Packet()) + else { + assert(false) + } - // There should be 1 scope. - #expect(execution_result.scopes.count == 1) + // There should be 1 scope. + #expect(execution_result.scopes.count == 1) - guard let scope = execution_result.scopes.current else { - assert(false) - } - - // We should be in the accept state. - #expect(state_result == Lang.accept) - - // There are two variables declared. - #expect(scope.count == 2) - - // Check the names/values of the variables in scope. - let b = try #require(scope.lookup(identifier: Identifier(name: "b"))) - let s = try #require(scope.lookup(identifier: Identifier(name: "s"))) - #expect(b.value_type == ValueType.Boolean(false)) - #expect(s.value_type == ValueType.String("\"testing\"")) + guard let scope = execution_result.scopes.current else { + assert(false) + } + // We should be in the accept state. + #expect(state_result == Lang.accept) + // There are two variables declared. + #expect(scope.count == 2) + // Check the names/values of the variables in scope. + let b = try #require(scope.lookup(identifier: Identifier(name: "b"))) + let s = try #require(scope.lookup(identifier: Identifier(name: "s"))) + #expect(b.value_type.eq(rhs: P4BooleanValue(withValue: false))) + #expect(s.value_type.eq(rhs: P4StringValue(withValue: "\"testing\""))) }