Refactor Parsing/Runtime

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-02-24 00:28:41 -05:00
parent f9353c683e
commit 64a0fe4255
29 changed files with 1269 additions and 974 deletions
+13 -13
View File
@@ -10,20 +10,20 @@ let package = Package(
products: [ products: [
// Products define the executables and libraries a package produces, making them visible to other packages. // Products define the executables and libraries a package produces, making them visible to other packages.
.library( .library(
name: "Parser", name: "P4Parser",
targets: ["Parser"] targets: ["P4Parser"]
), ),
.library( .library(
name: "Common", name: "Common",
targets: ["Common"] targets: ["Common"]
), ),
.library( .library(
name: "Lang", name: "P4Lang",
targets: ["Lang"] targets: ["P4Lang"]
), ),
.library( .library(
name: "Runtime", name: "P4Runtime",
targets: ["Runtime"] targets: ["P4Runtime"]
), ),
], ],
dependencies: [ dependencies: [
@@ -40,15 +40,15 @@ let package = Package(
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"), .product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
]), ]),
.target( .target(
name: "Parser", name: "P4Parser",
dependencies: [ dependencies: [
.product(name: "SwiftTreeSitter", package: "swift-tree-sitter"), .product(name: "SwiftTreeSitter", package: "swift-tree-sitter"),
.product(name: "SwiftTreeSitterLayer", package: "swift-tree-sitter"), .product(name: "SwiftTreeSitterLayer", package: "swift-tree-sitter"),
.product(name: "TreeSitterP4", package: "tree-sitter-p4"), .product(name: "TreeSitterP4", package: "tree-sitter-p4"),
.target(name: "TreeSitterExtensions"), .target(name: "TreeSitterExtensions"),
.target(name: "Common"), .target(name: "Common"),
.target(name: "Lang"), .target(name: "P4Lang"),
.target(name: "Runtime"), .target(name: "P4Runtime"),
], ],
), ),
.target( .target(
@@ -63,16 +63,16 @@ let package = Package(
dependencies: ["Macros"] dependencies: ["Macros"]
), ),
.target( .target(
name: "Lang", name: "P4Lang",
dependencies: ["Common"] dependencies: ["Common"]
), ),
.target( .target(
name: "Runtime", name: "P4Runtime",
dependencies: ["Lang", "Common"] dependencies: ["P4Lang", "Common"]
), ),
.testTarget( .testTarget(
name: "ParserTests", name: "ParserTests",
dependencies: ["Parser", "Runtime", "Lang", "Macros", "TreeSitterExtensions", "Common"] dependencies: ["P4Parser", "P4Runtime", "P4Lang", "Macros", "TreeSitterExtensions", "Common"]
), ),
] ]
) )
+3 -128
View File
@@ -16,7 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
open class ProgramExecution: CustomStringConvertible { open class ProgramExecution: CustomStringConvertible {
public var scopes: Scopes = Scopes() public var scopes: ValueScopes = ValueScopes()
var error: Error? var error: Error?
var debug: DebugLevel = DebugLevel.Error var debug: DebugLevel = DebugLevel.Error
@@ -75,130 +75,5 @@ open class ProgramExecution: CustomStringConvertible {
} }
public typealias ValueScope = Scope<P4Value>
public struct Scope: CustomStringConvertible, Equatable { public typealias ValueScopes = Scopes<P4Value>
var variables: [Variable] = Array()
public init() {}
public var description: String {
var result = String()
for v in variables {
result += "\(v)\n"
}
return result
}
public var count: Int {
get {
variables.count
}
}
public func set(identifier: Identifier, value: P4Value) -> Scope? {
var updated = false
var updated_scope: [Variable] = Array()
for v in variables {
if v == identifier && v.value_type.type().eq(rhs: value.type()) {
updated = true
updated_scope.append(Variable(name: v.name, withValue: value, isConstant: false))
} else {
updated_scope.append(v)
}
}
var new_scope = Scope()
new_scope.variables = updated_scope
return if updated {
new_scope
} else {
.none
}
}
public func lookup(identifier: Identifier) -> Variable? {
for v in variables {
if v == identifier {
return v
}
}
return .none
}
public mutating func declare(variable: Variable) -> Scope {
var s = self
s.variables.append(variable)
return s
}
}
public struct Scopes: CustomStringConvertible, Equatable {
var scopes: [Scope] = Array()
public init() {}
init(withScopes scopes: [Scope]) {
self.scopes = scopes
}
public func enter() -> Scopes {
var new_scopes = scopes
new_scopes.append(Scope())
return Scopes(withScopes: new_scopes)
}
public func exit() -> Scopes {
var old_scopes = scopes
_ = old_scopes.popLast()
return Scopes(withScopes: old_scopes)
}
public var description: String {
var result = String()
for s in scopes {
result += "Scope:\n\(s)\n"
}
return result
}
public var current: Scope? {
get {
scopes.last
}
}
public func declare(variable: Variable) -> Scopes {
var s = self
if var scope = s.scopes.popLast() {
s.scopes.append(scope.declare(variable: variable))
}
return s
}
public func evaluate(identifier: Identifier) -> Result<P4Value> {
for scope in scopes {
if let vari = scope.lookup(identifier: identifier) {
return .Ok(vari.value)
}
}
return .Error(Error(withMessage: "Cannot find \(identifier) in scope."))
}
public var count: Int {
get {
scopes.count
}
}
public func set(identifier: Identifier, value: P4Value) -> Scopes {
var new_scopes: [Scope] = Array()
for scope in self.scopes {
if let updated_scope = scope.set(identifier: identifier, value: value) {
new_scopes.append(updated_scope)
} else {
new_scopes.append(scope)
}
}
return Scopes(withScopes: new_scopes)
}
}
+34 -11
View File
@@ -16,7 +16,11 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
/// A P4 identifier /// A P4 identifier
public class Identifier: CustomStringConvertible, Equatable { public class Identifier: CustomStringConvertible, Equatable, Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(self.name)
}
var name: String var name: String
public init(name: String) { public init(name: String) {
@@ -32,22 +36,38 @@ public class Identifier: CustomStringConvertible, Equatable {
} }
} }
/// A P4 variable /// A P4 identifier
public class Variable: Identifier { public class TypedIdentifier: Identifier {
var constant: Bool public var parsed_type: P4Type
var value: P4Value
public init(name: String, withValue value: P4Value, isConstant constant: Bool) { public init(name: String, withType type: P4Type) {
self.parsed_type = type
super.init(name: name)
}
public override var description: String {
return "\(name)"
}
}
/// A P4 variable
public class Variable: TypedIdentifier {
var constant: Bool
var value: P4Value?
public init(
name: String, withType type: P4Type, withValue value: P4Value?, isConstant constant: Bool
) {
self.constant = constant self.constant = constant
self.value = value self.value = value
super.init(name: name) super.init(name: name, withType: type)
} }
public override var description: String { public override var description: String {
return "\(super.description) = \(value) \(constant ? "(constant)" : "")" return "\(super.description) = \(value) \(constant ? "(constant)" : "")"
} }
public var value_type: P4Value { public var value_type: P4Value? {
value value
} }
} }
@@ -78,6 +98,7 @@ extension P4ValueBase: EvaluatableExpression {
/// The type for a P4 struct /// The type for a P4 struct
public struct P4Struct: P4Type { public struct P4Struct: P4Type {
public let name: String public let name: String
// The type of the struct created is always anonymous. // The type of the struct created is always anonymous.
public static func create() -> any P4Type { public static func create() -> any P4Type {
@@ -105,10 +126,10 @@ public struct P4Struct: P4Type {
/// The field of a P4 struct /// The field of a P4 struct
public struct P4StructField { public struct P4StructField {
public let name: Identifier public let name: TypedIdentifier
public let type: P4Type public let type: P4Type
public init(withName name: Identifier, withType type: P4Type) { public init(withName name: TypedIdentifier, withType type: P4Type) {
self.name = name self.name = name
self.type = type self.type = type
} }
@@ -124,6 +145,7 @@ public class P4StructValue: P4ValueBase<P4Struct> {
/// A P4 boolean type /// A P4 boolean type
public struct P4Boolean: P4Type { public struct P4Boolean: P4Type {
public static func create() -> any P4Type { public static func create() -> any P4Type {
return P4Boolean() return P4Boolean()
} }
@@ -192,13 +214,14 @@ public class P4IntValue: P4ValueBase<P4Int> {
/// A P4 string type /// A P4 string type
public struct P4String: P4Type { public struct P4String: P4Type {
public static func create() -> any P4Type { public static func create() -> any P4Type {
return P4String() return P4String()
} }
public var description: String { public var description: String {
return "String" return "String"
} }
public func eq(rhs: P4Type) -> Bool { public func eq(rhs: any P4Type) -> Bool {
return switch rhs { return switch rhs {
case is P4String: true case is P4String: true
default: false default: false
+5 -4
View File
@@ -21,9 +21,10 @@ public protocol EvaluatableExpression {
/// - execution: The execution context in which to evaluate the expression /// - execution: The execution context in which to evaluate the expression
/// - Returns: The value of expression /// - Returns: The value of expression
func evaluate(execution: ProgramExecution) -> Result<P4Value> func evaluate(execution: ProgramExecution) -> Result<P4Value>
func type() -> any P4Type
} }
public protocol EvaluatableParserStatement { public protocol EvaluatableStatement {
/// Evaluate a statement for a given execution /// Evaluate a statement for a given execution
/// - Parameters /// - Parameters
/// - execution: The execution context in which to evaluate the parser statement /// - execution: The execution context in which to evaluate the parser statement
@@ -32,11 +33,11 @@ public protocol EvaluatableParserStatement {
} }
public protocol P4Type: CustomStringConvertible { public protocol P4Type: CustomStringConvertible {
static func create() -> P4Type static func create() -> any P4Type
func eq(rhs: P4Type) -> Bool func eq(rhs: any P4Type) -> Bool
} }
public protocol P4Value: CustomStringConvertible { public protocol P4Value: CustomStringConvertible {
func type() -> P4Type func type() -> any P4Type
func eq(rhs: P4Value) -> Bool func eq(rhs: P4Value) -> Bool
} }
+6 -3
View File
@@ -61,12 +61,16 @@ public enum DebugLevel {
} }
} }
public struct Error: Equatable { public struct Error: Equatable, CustomStringConvertible {
public private(set) var msg: String public private(set) var msg: String
public init(withMessage msg: String) { public init(withMessage msg: String) {
self.msg = msg self.msg = msg
} }
public var description: String {
return self.msg
}
} }
public struct Nothing: CustomStringConvertible { public struct Nothing: CustomStringConvertible {
@@ -77,7 +81,6 @@ public struct Nothing: CustomStringConvertible {
public init() {} public init() {}
} }
public enum Result<OKT>: Equatable { public enum Result<OKT>: Equatable {
case Ok(OKT) case Ok(OKT)
case Error(Error) case Error(Error)
@@ -119,7 +122,7 @@ extension Result where OKT: CustomStringConvertible {
} }
} }
extension Result { extension Result: CustomStringConvertible {
public var description: String { public var description: String {
switch self { switch self {
case Result.Error(let e): case Result.Error(let e):
+11 -8
View File
@@ -62,7 +62,7 @@ public struct RequireResult: ExpressionMacro {
return """ return """
{ {
switch \(argument) { switch \(argument) {
case Result.Ok(let _): return true case Result.Ok(_): return true
case Result.Error(let __error): case Result.Error(let __error):
print("Unexpected result: \\(__error)") print("Unexpected result: \\(__error)")
return false return false
@@ -79,18 +79,21 @@ public struct RequireErrorResult: ExpressionMacro {
) throws -> ExprSyntax { ) throws -> ExprSyntax {
let arguments = node.argumentList.indices let arguments = node.argumentList.indices
let expected_error = node.argumentList[arguments.startIndex] let expected_error = node.argumentList[arguments.startIndex].expression
let error_producer = node.argumentList[arguments.index(after: arguments.startIndex)] let error_producer = node.argumentList[arguments.index(after: arguments.startIndex)].expression
return """ return ExprSyntax("""
{ {
if case Result.Error(\(expected_error)) = \(error_producer) { let __expected_error = \(expected_error)
true let __actual_error = \(error_producer)
if case Result.Error(__expected_error) = __actual_error {
return true
} else { } else {
false print("Expected Error: \\(__expected_error) but got Error: \\(__actual_error)")
return false
} }
}() }()
""" """)
} }
} }
@@ -26,10 +26,10 @@ public struct LocalElement {
} }
public struct ParserAssignmentStatement { public struct ParserAssignmentStatement {
public let lvalue: Identifier public let lvalue: TypedIdentifier
public let value: P4Value public let value: EvaluatableExpression
public init(withLValue lvalue: Identifier, withValue value: P4Value) { public init(withLValue lvalue: TypedIdentifier, withValue value: EvaluatableExpression) {
self.lvalue = lvalue self.lvalue = lvalue
self.value = value self.value = value
} }
@@ -96,17 +96,19 @@ public struct ParserTransitionStatement {
} }
public struct VariableDeclarationStatement { public struct VariableDeclarationStatement {
public var variable: Variable public var initializer: EvaluatableExpression
public init(withVariable variable: Variable) { public var identifier: Identifier
self.variable = variable public init(identifier: Identifier, withInitializer initializer: EvaluatableExpression) {
self.identifier = identifier
self.initializer = initializer
} }
} }
public class ParserState: Equatable, CustomStringConvertible, Comparable { public class ParserState: Equatable, CustomStringConvertible, Comparable {
public private(set) var state_name: String public private(set) var state_name: String
public private(set) var local_elements: [EvaluatableParserStatement] public private(set) var local_elements: [EvaluatableStatement]
public private(set) var statements: [EvaluatableParserStatement] public private(set) var statements: [EvaluatableStatement]
public private(set) var transition: ParserTransitionStatement? public private(set) var transition: ParserTransitionStatement?
public private(set) var next_state: ParserState? public private(set) var next_state: ParserState?
@@ -127,8 +129,8 @@ public class ParserState: Equatable, CustomStringConvertible, Comparable {
/// Construct a ParserState /// Construct a ParserState
public init( public init(
name: String, withLocalElements localElements: [EvaluatableParserStatement]?, name: String, withLocalElements localElements: [EvaluatableStatement]?,
withStatements stmts: [EvaluatableParserStatement]?, withStatements stmts: [EvaluatableStatement]?,
withTransition transitionStatement: ParserTransitionStatement withTransition transitionStatement: ParserTransitionStatement
) { ) {
state_name = name state_name = name
+205
View File
@@ -0,0 +1,205 @@
// 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 P4Lang
import SwiftTreeSitter
import TreeSitterP4
protocol ParseableEvaluatableExpression {
static func parse(
node: Node, inTree tree: MutableTree, withScopes scopes: LexicalScopes
) -> Result<EvaluatableExpression?>
}
extension TypedIdentifier: ParseableEvaluatableExpression {
static func parse(
node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree, withScopes scopes: LexicalScopes
) -> Result<EvaluatableExpression?> {
guard
let parser_statement_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(expression (identifier) @identifier)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let qr = parser_statement_query.execute(node: node, in: tree)
guard let result = qr.next() else {
return .Ok(.none)
}
let value_capture = result.captures(named: "identifier")
guard
case Result.Ok(let type) = scopes.lookup(
identifier: Identifier(name: value_capture[0].node.text!))
else {
return .Error(Error(withMessage: "Cannot find \(result.captures[0].node.text!) in scope"))
}
return .Ok(TypedIdentifier(name: result.captures[0].node.text!, withType: type))
}
}
extension P4BooleanValue: ParseableEvaluatableExpression {
static func parse(
node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree, withScopes scopes: LexicalScopes
) -> Result<EvaluatableExpression?> {
guard
let true_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(expression (true))"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let true_qr = true_query.execute(node: node, in: tree)
if true_qr.next() != nil {
return .Ok(P4BooleanValue(withValue: true))
}
guard
let false_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(expression (false))"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let false_qr = false_query.execute(node: node, in: tree)
if false_qr.next() != nil {
return .Ok(P4BooleanValue(withValue: false))
}
return .Ok(.none)
}
}
extension P4IntValue: ParseableEvaluatableExpression {
static func parse(
node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree, withScopes scopes: LexicalScopes
) -> Result<EvaluatableExpression?> {
guard
let integer_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(expression (integer) @value)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let integer_qr = integer_query.execute(node: node, in: tree)
guard let result = integer_qr.next() else {
return .Ok(.none)
}
let value_capture = result.captures(named: "value")
if let parsed_int = Int(value_capture[0].node.text!) {
return .Ok(P4IntValue(withValue: parsed_int))
} else {
print("HERE!!")
return .Error(Error(withMessage: "Failed to parse integer: \(result.captures[0].node.text!)"))
}
}
}
extension P4StringValue: ParseableEvaluatableExpression {
static func parse(
node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree, withScopes scopes: LexicalScopes
) -> Result<EvaluatableExpression?> {
guard
let string_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(expression (string_literal) @value)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let string_qr = string_query.execute(node: node, in: tree)
guard let result = string_qr.next() else {
return .Ok(.none)
}
return .Ok(P4StringValue(withValue: result.captures[0].node.text!))
}
}
struct Expression {
public static func Parse(
node: Node, inTree: MutableTree, withScopes scopes: LexicalScopes
) -> Result<EvaluatableExpression> {
let localElementsParsers: [ParseableEvaluatableExpression.Type] = [
P4BooleanValue.self, P4StringValue.self, P4IntValue.self, TypedIdentifier.self,
]
for le_parser in localElementsParsers {
if case Result.Ok(.some(let parsed)) = le_parser.parse(
node: node, inTree: inTree, withScopes: scopes)
{
return .Ok(parsed)
}
}
return Result.Error(Error(withMessage: "Could not parse into expression."))
}
}
extension ExpressionStatement: ParseableStatement {
public static func Parse(
node: Node, inTree tree: MutableTree, withScopes scopes: LexicalScopes
) -> Result<(EvaluatableStatement?, LexicalScopes)> {
guard
let expression_statement_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(expressionStatement (expression) @expression)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Ok((.none, scopes))
}
let qr = expression_statement_query.execute(node: node, in: tree)
guard let query_result = qr.next() else {
return Result.Ok((.none, scopes))
}
let expression_capture = query_result.captures(named: "expression")
if !expression_capture.isEmpty {
// TODO: Actually create an ExpressionStatement
return Result.Ok((ExpressionStatement(), scopes))
}
return Result.Ok((.none, scopes))
}
}
+387
View File
@@ -0,0 +1,387 @@
// 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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
let p4lang = Language(tree_sitter_p4())
extension ParserAssignmentStatement: ParseableStatement {
public static func Parse(
node: Node, inTree tree: MutableTree, withScopes scopes: LexicalScopes
) -> Result<(EvaluatableStatement?, LexicalScopes)> {
guard
let parser_assignment_statement_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(assignmentStatement (expression) @lvalue (assignment) (expression) @value)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Ok((.none, scopes))
}
let qr = parser_assignment_statement_query.execute(node: node, in: tree)
guard let parser_assignment_statement = qr.next() else {
return Result.Ok((.none, scopes))
}
let lvalue_capture = parser_assignment_statement.captures(named: "lvalue")
let rvalue_capture = parser_assignment_statement.captures(named: "value")
// There must be a type name and a variable name
guard !lvalue_capture.isEmpty,
!rvalue_capture.isEmpty,
let lvalue_expression_raw = lvalue_capture[0].node.text
else {
return Result.Error(
Error(withMessage: "Could not parse a parser assignment statement"))
}
let rvalue_raw = rvalue_capture[0].node
let maybe_parsed_rvalue = Expression.Parse(node: rvalue_raw, inTree: tree, withScopes: scopes)
guard case Result.Ok(let rvalue) = maybe_parsed_rvalue else {
return Result.Error(maybe_parsed_rvalue.error()!)
}
let lvalue_identifier = Identifier(name: lvalue_expression_raw)
guard case Result.Ok(let lvalue_type) = scopes.lookup(identifier: lvalue_identifier) else {
return Result.Error(
Error(withMessage: "Cannot assign to variable \(lvalue_identifier) not in scope"))
}
if rvalue.type().eq(rhs: lvalue_type) {
return Result.Ok(
(
ParserAssignmentStatement(
withLValue: TypedIdentifier(name: lvalue_expression_raw, withType: lvalue_type),
withValue: rvalue
), scopes
))
} else {
return Result.Error(
Error(
withMessage:
"Cannot assign value of type \(rvalue.type()) to \(lvalue_identifier) (with type \(lvalue_type))"
))
}
}
}
public struct Parser {
public struct LocalElements {
static func Parse(
node: Node, inTree tree: MutableTree, withScope scopes: LexicalScopes
) -> Result<(EvaluatableStatement, LexicalScopes)> {
let localElementsParsers: [ParseableStatement.Type] = [
VariableDeclarationStatement.self
]
for local_element_parser in localElementsParsers {
switch local_element_parser.Parse(node: node, inTree: tree, withScopes: scopes) {
case Result.Ok((.some(let parsed), let parsed_updated_scopes)):
return Result.Ok((parsed, parsed_updated_scopes))
case Result.Error(let e):
return Result.Error(Error(withMessage: "Failed to parse local element: \(e)"))
default: continue
}
}
return Result.Error(
Error(
withMessage:
"Failed to parse any local elements from specified local elements: \(node.text!)")
)
}
}
public struct Statements {
static func Parse(
node: Node, inTree tree: MutableTree, withScope scopes: LexicalScopes
) -> Result<(EvaluatableStatement, LexicalScopes)> {
let statementParsers: [ParseableStatement.Type] = [
ParserAssignmentStatement.self, ExpressionStatement.self,
VariableDeclarationStatement.self,
]
// Iterate through statement parsers and give each one a chance.
for parser in statementParsers {
switch parser.Parse(node: node, inTree: tree, withScopes: scopes) {
case Result.Ok((.some(let parsed), let updatedLexicalScopes)):
return .Ok((parsed, updatedLexicalScopes))
case Result.Error(let e):
return .Error(Error(withMessage: "Failed to parse a statement element: \(e)"))
default: continue
}
}
return Result.Error(
Error(withMessage: "Failed to parse a statement element: \(node.text!)"))
}
}
public struct TransitionSelectExpressionCaseStatement {
static func Parse(
node: Node, inTree tree: MutableTree, withLexicalScopes scopes: LexicalScopes
) -> Result<([KeysetExpression], LexicalScopes)> {
guard
let transition_selection_expression_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"((keysetExpression (expression) @ks) (colon) (identifier) @next-state)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let qr = transition_selection_expression_query.execute(node: node, in: tree)
var kses: [KeysetExpression] = Array()
for expression in qr {
let next_state_name = expression.captures[1].node.text!
if case .Error(let e) = Expression.Parse(
node: expression.captures[0].node, inTree: tree, withScopes: scopes
)
.map(block: { expression in
kses.append(
KeysetExpression(
withKey: expression, withNextStateName: next_state_name))
return .Ok(expression)
}) {
return .Error(e)
}
}
return .Ok((kses, scopes))
}
}
public struct TransitionSelectExpression {
static func Parse(
node: Node, inTree tree: MutableTree, withScope scopes: LexicalScopes
) -> Result<(ParserTransitionStatement?, LexicalScopes)> {
guard
let transition_selection_expression_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(parserTransitionStatement (transition) (transitionSelectionExpression (selectExpression (select) (expression) @selector (selectBody) @body)))"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let qr = transition_selection_expression_query.execute(node: node, in: tree)
guard let query_result = qr.next() else {
return .Error(Error(withMessage: "Could not find transition select expression"))
}
let selector = query_result.captures(named: "selector")
let body = query_result.captures(named: "body")
return Expression.Parse(node: selector[0].node, inTree: tree, withScopes: scopes).map {
expression in
return
switch TransitionSelectExpressionCaseStatement.Parse(
node: body[0].node, inTree: tree, withLexicalScopes: scopes)
{
case .Ok((let kse, let newLexicalScopes)):
Result<(ParserTransitionStatement?, LexicalScopes)>.Ok(
(
ParserTransitionStatement(
withTransitionExpression: ParserTransitionSelectExpression(
withSelector: expression, withKeysetExpressions: kse)), newLexicalScopes
))
case .Error(let e): Result.Error(e)
}
}
}
}
public struct TransitionStatement {
static func Parse(
node: Node, inTree tree: MutableTree, withScope scopes: LexicalScopes
) -> Result<(ParserTransitionStatement?, LexicalScopes)> {
guard
let next_state_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(parserTransitionStatement (transition) (transitionSelectionExpression (identifier) @next-state))"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let qr = next_state_query.execute(node: node, in: tree)
if let next_state_result = qr.next() {
let transition_capture = next_state_result.captures(named: "next-state")
return .Ok(
(ParserTransitionStatement(withNextState: transition_capture[0].node.text!), scopes))
}
return TransitionSelectExpression.Parse(node: node, inTree: tree, withScope: scopes)
}
}
public struct State {
static func Parse(
node: Node, inTree tree: MutableTree, withLexicalScopes scopes: LexicalScopes
) -> Result<(ParserState, LexicalScopes)> {
guard
let parser_state_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(parserState (state) (identifier) @state-name (parserLocalElements ((parserLocalElement) @state-local-element (semicolon))*)* (parserStatements ((parserStatement) @state-statement (semicolon))*)* (parserTransitionStatement) @transition)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let qr = parser_state_query.execute(node: node, in: tree)
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-element")
let statements_capture = parser_declaration.captures(named: "state-statement")
var parsed_les: [EvaluatableStatement] = Array()
var parse_err: Error? = .none
var current_scopes = scopes.enter()
defer {
current_scopes = current_scopes.exit()
}
for state_le in state_le_capture {
state_le.node.enumerateChildren { node in
switch LocalElements.Parse(
node: node, inTree: tree, withScope: current_scopes)
{
case .Ok((let le, let le_parsed_scopes)):
current_scopes = le_parsed_scopes
parsed_les.append(le)
case .Error(let e):
parse_err = e
}
}
}
if let parse_err = parse_err {
return Result.Error(parse_err)
}
var parsed_s: [EvaluatableStatement] = Array()
if !statements_capture.isEmpty {
for statement in statements_capture {
statement.node.enumerateChildren { node in
switch Statements.Parse(
node: node, inTree: tree, withScope: current_scopes)
{
case .Ok((let le, let le_parsed_scopes)):
current_scopes = le_parsed_scopes
parsed_s.append(le)
case .Error(let e):
parse_err = e
}
}
}
}
if let parse_err = parse_err {
return Result.Error(parse_err)
}
print("current_scopes: \(current_scopes)")
// TODO: Now that scopes are involved, doing this out of order will not work!
guard !state_name_capture.isEmpty,
!transition_capture.isEmpty,
let parsed_state_name = state_name_capture[0].node.text
else {
return Result.Error(Error(withMessage: "Could not parse a parser declaration"))
}
switch TransitionStatement.Parse(
node: transition_capture[0].node, inTree: tree, withScope: current_scopes)
{
case .Ok((.some(let transition_statement), let current_scopes)):
return Result.Ok(
(
ParserState(
name: parsed_state_name, withLocalElements: parsed_les,
withStatements: parsed_s,
withTransition: transition_statement), current_scopes
))
case .Error(let e): return .Error(e)
case .Ok((.none, _)): return .Error(Error(withMessage: "Missing transition statement"))
}
}
}
static func Parse(
withName name: Identifier, node: Node, inTree tree: MutableTree,
withLexicalScopes scopes: LexicalScopes
) -> Result<(P4Lang.Parser, LexicalScopes)> {
guard
let parser_state_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(parserStates) @parser-states"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(
Error(withMessage: "Could not compile the parser state tree sitter query"))
}
var parser = P4Lang.Parser(withName: name)
// Build a state from each one listed.
let qr = parser_state_query.execute(node: node, in: tree)
let qr_value = qr.next()!
let captures = qr_value.captures(named: "parser-states")
var error: Error? = .none
var parser_scopes = scopes
// TODO: Assert that there is only one.
captures[0].node.enumerateChildren { parser_state in
switch Parser.State.Parse(node: parser_state, inTree: tree, withLexicalScopes: scopes) {
case Result.Ok(let (state, new_parser_scopes)):
parser.states = parser.states.append(state: state)
parser_scopes = new_parser_scopes
case Result.Error(let e): error = e
}
}
if let error = error {
return .Error(error)
}
return Result.Ok((parser, parser_scopes))
}
}
+75
View File
@@ -0,0 +1,75 @@
// 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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
public struct Program {
public static func Parse(_ source: String) -> Result<P4Lang.Program> {
let p = SwiftTreeSitter.Parser.init()
do {
try p.setLanguage(p4lang)
} catch {
return Result.Error(Error(withMessage: "Could not configure the P4 parser"))
}
let result = p.parse(source)
guard let tree = result,
!tree.isError(lang: p4lang),
!tree.containsMissing(lang: p4lang)
else {
return Result.Error(Error(withMessage: "Could not compile the P4 program"))
}
guard
let parser_declaration_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(parserDeclaration (parserType parser_name: (identifier) @parser-name) (parserStates) @parser-states)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(
Error(withMessage: "Could not compile the parser declaration tree sitter query"))
}
var program = P4Lang.Program()
// Set up a lexical scope for parsing.
var program_scope = LexicalScopes().enter()
let parser_qc = parser_declaration_query.execute(in: tree)
for parser_declaration in parser_qc {
switch Parser.Parse(
withName: Identifier(name: parser_declaration.nodes[0].text!),
node: parser_declaration.nodes[1], inTree: tree, withLexicalScopes: program_scope)
{
case Result.Ok((let parser, let new_program_scope)):
program.types.append(parser)
program_scope = new_program_scope
case Result.Error(let error): return Result.Error(error)
}
}
return Result.Ok(program)
}
}
+37
View File
@@ -0,0 +1,37 @@
// 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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
public protocol ParseableStatement {
static func Parse(
node: Node, inTree tree: MutableTree, withScopes scopes: LexicalScopes
) -> Result<(EvaluatableStatement?, LexicalScopes)>
}
public protocol ParseableValue {
static func ParseValue(withValue value: String) -> Result<P4Value>
}
public protocol ParseableType {
static func ParseType(type: String) -> Result<P4Type?>
}
+86
View File
@@ -0,0 +1,86 @@
// 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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
extension VariableDeclarationStatement: ParseableStatement {
public static func Parse(
node: Node, inTree tree: MutableTree, withScopes scopes: LexicalScopes
) -> Result<(EvaluatableStatement?, LexicalScopes)> {
guard
let variable_declaration_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(variableDeclaration (annotations)? (typeRef) @type-name variable_name: (identifier) @identifier ((assignment) (expression) @value)?)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Ok((.none, scopes))
}
let qr = variable_declaration_query.execute(node: node, in: tree)
guard let variable_declaration = qr.next() else {
return .Ok((.none, scopes))
}
let type_name_capture = variable_declaration.captures(named: "type-name")
let variable_name_capture = variable_declaration.captures(named: "identifier")
let value_capture = variable_declaration.captures(named: "value")
// There must be a type name and a variable name
guard !type_name_capture.isEmpty,
!variable_name_capture.isEmpty,
!value_capture.isEmpty,
let variable_name = variable_name_capture[0].node.text,
let type_name = type_name_capture[0].node.text
else {
return Result.Error(
Error(withMessage: "Could not parse a parser variable declaration statement"))
}
guard case .Ok(let declaration_p4_type) = Types.ParseBasicType(type: type_name) else {
return Result.Error(
Error(withMessage: "Could not parse a P4 type from \(type_name)"))
}
let rvalue_raw = value_capture[0].node
let maybe_parsed_rvalue = Expression.Parse(node: rvalue_raw, inTree: tree, withScopes: scopes)
guard case Result.Ok(let rvalue) = maybe_parsed_rvalue else {
return Result.Error(maybe_parsed_rvalue.error()!)
}
if rvalue.type().eq(rhs: declaration_p4_type) {
return Result.Ok(
(
VariableDeclarationStatement(identifier: Identifier(name: variable_name), withInitializer: rvalue),
scopes.declare(identifier: Identifier(name: variable_name), withValue: declaration_p4_type)
))
} else {
return Result.Error(
Error(
withMessage:
"Cannot initialize \(variable_name) (with type \(declaration_p4_type)) from rvalue with type \(rvalue.type())"
))
}
}
}
+54
View File
@@ -0,0 +1,54 @@
// 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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
extension P4Boolean: ParseableType {
public static func ParseType(type: String) -> Common.Result<(any Common.P4Type)?> {
return type == "bool" ? .Ok(P4Boolean.create()) : .Ok(.none)
}
}
extension P4Int: ParseableType {
public static func ParseType(type: String) -> Common.Result<(any Common.P4Type)?> {
return type == "int" ? .Ok(P4Int.create()) : .Ok(.none)
}
}
extension P4String: ParseableType {
public static func ParseType(type: String) -> Common.Result<(any Common.P4Type)?> {
return type == "string" ? .Ok(P4String.create()) : .Ok(.none)
}
}
public struct Types {
static func ParseBasicType(type: String) -> Result<P4Type> {
let type_parsers: [ParseableType.Type] = [P4Boolean.self, P4Int.self, P4String.self]
for type_parser in type_parsers {
switch type_parser.ParseType(type: type) {
case .Ok(.some(let type)): return .Ok(type)
case .Ok(.none): continue
case .Error(let e): return .Error(e)
}
}
return Result.Error(Error(withMessage: "Type name not recognized"))
}
}
@@ -16,11 +16,14 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import Common import Common
import Lang import P4Lang
extension ParserAssignmentStatement: EvaluatableParserStatement { extension ParserAssignmentStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> ProgramExecution { public func evaluate(execution: ProgramExecution) -> ProgramExecution {
let updated_scopes = execution.scopes.set(identifier: self.lvalue, value: self.value) guard case Result.Ok(let value) = self.value.evaluate(execution: execution) else {
return execution.setError(error: Error(withMessage: "AHHHHH"))
}
let updated_scopes = execution.scopes.set(identifier: self.lvalue, withValue: value)
execution.scopes = updated_scopes execution.scopes = updated_scopes
@@ -53,7 +56,7 @@ public struct ParserStateDirectTransition: ParserStateInstance {
return false return false
} }
public func current() -> Lang.ParserState { public func current() -> P4Lang.ParserState {
return currrent_state return currrent_state
} }
@@ -72,7 +75,7 @@ public struct ParserStateNoTransition: ParserStateInstance {
return true return true
} }
public func current() -> Lang.ParserState { public func current() -> P4Lang.ParserState {
return currrent_state return currrent_state
} }
} }
@@ -116,7 +119,7 @@ public struct ParserStateSelectTransition: ParserStateInstance {
return false return false
} }
public func current() -> Lang.ParserState { public func current() -> P4Lang.ParserState {
return currrent_state return currrent_state
} }
@@ -15,26 +15,33 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import Lang import P4Lang
import Common import Common
extension VariableDeclarationStatement: EvaluatableParserStatement { extension VariableDeclarationStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> ProgramExecution { public func evaluate(execution: ProgramExecution) -> ProgramExecution {
let new_scopes = execution.scopes.declare(variable: self.variable) guard case .Ok(let initial_value) = self.initializer.evaluate(execution: execution) else {
return execution.setError(error: Error(withMessage: "Could not evaluate \(self.initializer)"))
}
let new_scopes = execution.scopes.declare(identifier: self.identifier, withValue: initial_value)
execution.scopes = new_scopes execution.scopes = new_scopes
return execution return execution
} }
} }
extension ExpressionStatement: EvaluatableParserStatement { extension ExpressionStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> ProgramExecution { public func evaluate(execution: ProgramExecution) -> ProgramExecution {
return execution return execution
} }
} }
// Variables are evaluatable because they can be looked up by identifiers. // Variables are evaluatable because they can be looked up by identifiers.
extension Identifier: EvaluatableExpression { extension TypedIdentifier: EvaluatableExpression {
public func type() -> any Common.P4Type {
return self.parsed_type
}
public func evaluate(execution: Common.ProgramExecution) -> Result<P4Value> { public func evaluate(execution: Common.ProgramExecution) -> Result<P4Value> {
return execution.scopes.evaluate(identifier: self) return execution.scopes.lookup(identifier: self)
} }
} }
@@ -16,7 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import Common import Common
import Lang import P4Lang
protocol EvaluatableParserTransition { protocol EvaluatableParserTransition {
func program(execution: ProgramExecution) -> (ParserState, ProgramExecution) func program(execution: ProgramExecution) -> (ParserState, ProgramExecution)
@@ -16,7 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import Common import Common
import Lang import P4Lang
/// The runtime for a parser /// The runtime for a parser
public class ParserRuntime: CustomStringConvertible { public class ParserRuntime: CustomStringConvertible {
@@ -27,12 +27,12 @@ public class ParserRuntime: CustomStringConvertible {
} }
/// Create a parser runtime from a P4 program /// Create a parser runtime from a P4 program
public static func create(program: Lang.Program) -> Result<ParserRuntime> { public static func create(program: P4Lang.Program) -> Result<ParserRuntime> {
return switch program.starting_parser() { return switch program.starting_parser() {
case .Ok(let parser): case .Ok(let parser):
switch ParserInstance.compile(parser) { switch ParserInstance.compile(parser) {
case .Ok(let execution): .Ok(Runtime.ParserRuntime(execution: execution)) case .Ok(let execution): .Ok(P4Runtime.ParserRuntime(execution: execution))
case .Error(let error): .Error(error) case .Error(let error): .Error(error)
} }
case .Error(let error): .Error(error) case .Error(let error): .Error(error)
-107
View File
@@ -1,107 +0,0 @@
// 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 SwiftTreeSitter
import TreeSitterP4
protocol ParseableEvaluatableExpression {
static func parse(node: Node, inTree tree: MutableTree, withScopes scopes: Scopes) -> Result<EvaluatableExpression?>
}
extension Identifier: ParseableEvaluatableExpression {
static func parse(
node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree, withScopes scopes: Scopes
) -> Result<EvaluatableExpression?> {
guard
let parser_statement_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(expression (identifier) @identifier)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let qr = parser_statement_query.execute(node: node, in: tree)
guard let result = qr.next() else {
return .Ok(.none)
}
return .Ok(Identifier(name: result.captures[0].node.text!))
}
}
extension P4BooleanValue: ParseableEvaluatableExpression {
static func parse(
node: SwiftTreeSitter.Node, inTree tree: SwiftTreeSitter.MutableTree, withScopes scopes: Scopes
) -> Result<EvaluatableExpression?> {
guard
let true_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(expression (true))"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let true_qr = true_query.execute(node: node, in: tree)
if true_qr.next() != nil {
return .Ok(P4BooleanValue(withValue: true))
}
guard
let false_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(expression (false))"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let false_qr = false_query.execute(node: node, in: tree)
if false_qr.next() != nil {
return .Ok(P4BooleanValue(withValue: false))
}
return .Ok(.none)
}
}
struct Expression {
public static func Parse(node: Node, inTree: MutableTree, withScopes scopes: Scopes) -> Result<EvaluatableExpression> {
let localElementsParsers: [ParseableEvaluatableExpression.Type] = [
P4BooleanValue.self, Identifier.self,
]
for le_parser in localElementsParsers {
if case Result.Ok(.some(let parsed)) = le_parser.parse(
node: node, inTree: inTree, withScopes: scopes)
{
return .Ok(parsed)
}
}
return Result.Error(Error(withMessage: "Could not parse into expression."))
}
}
-541
View File
@@ -1,541 +0,0 @@
// 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 Lang
import Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
let p4lang = Language(tree_sitter_p4())
public protocol ParseableParserStatement {
static func Parse(
node: Node, inTree tree: MutableTree, withScope scopes: Scopes
) -> Result<(EvaluatableParserStatement?, Scopes)>
}
extension ExpressionStatement: ParseableParserStatement {
public static func Parse(
node: Node, inTree tree: MutableTree, withScope scopes: Scopes
) -> Result<(EvaluatableParserStatement?, Scopes)> {
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, scopes))
}
let qr = parser_state_query.execute(node: node, in: tree)
guard let query_result = qr.next() else {
return Result.Ok((.none, scopes))
}
let expression_capture = query_result.captures(named: "expression")
if !expression_capture.isEmpty {
// TODO: Actually create an ExpressionStatement
return Result.Ok((ExpressionStatement(), scopes))
}
return Result.Ok((.none, scopes))
}
}
extension ParserAssignmentStatement: ParseableParserStatement {
public static func Parse(
node: Node, inTree tree: MutableTree, withScope scopes: Scopes
) -> Result<(EvaluatableParserStatement?, Scopes)> {
guard
let parser_state_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(assignmentStatement (expression) @lvalue (assignment) (expression) @value)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Ok((.none, scopes))
}
let qr = parser_state_query.execute(node: node, in: tree)
let parser_declaration = qr.next()!
let lvalue_capture = parser_declaration.captures(named: "lvalue")
let value_capture = parser_declaration.captures(named: "value")
// There must be a type name and a variable name
guard !lvalue_capture.isEmpty,
!value_capture.isEmpty,
let lvalue_expression_raw = lvalue_capture[0].node.text,
let value_capture_raw = value_capture[0].node.text
else {
return Result.Error(
Error(withMessage: "Could not parse a parser assignment statement"))
}
let lvalue_identifier = Identifier(name: lvalue_expression_raw)
let value =
if !value_capture_raw.isEmpty {
value_capture_raw
} else {
""
}
guard case Result.Ok(let declared_value) = scopes.evaluate(identifier: lvalue_identifier) else {
return Result.Error(
Error(withMessage: "Cannot assign to variable not in scope"))
}
return switch Parser.ParseValueType(type: declared_value.type(), withValue: value) {
case Result.Ok(let value_type):
Result.Ok(
(ParserAssignmentStatement(withLValue: lvalue_identifier, withValue: value_type), scopes))
case Result.Error(let e):
Result.Error(
Error(
withMessage:
"\(declared_value) has type \(declared_value.type()) but rvalue has mismatched type (\(e))"))
}
}
}
extension VariableDeclarationStatement: ParseableParserStatement {
public static func Parse(
node: Node, inTree tree: MutableTree, withScope scopes: Scopes
) -> Result<(EvaluatableParserStatement?, Scopes)> {
guard
let parser_state_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(variableDeclaration (annotations)? (typeRef) @type-name variable_name: (identifier) @identifier ((assignment) (expression) @value)?)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Ok((.none, scopes))
}
let qr = parser_state_query.execute(node: node, in: tree)
guard let parser_declaration = qr.next() else {
return .Ok((.none, scopes))
}
let type_name_capture = parser_declaration.captures(named: "type-name")
let variable_name_capture = parser_declaration.captures(named: "identifier")
let value_capture = parser_declaration.captures(named: "value")
// There must be a type name and a variable name
guard !type_name_capture.isEmpty,
!variable_name_capture.isEmpty,
let variable_name = variable_name_capture[0].node.text,
let type_name = type_name_capture[0].node.text
else {
return Result.Error(
Error(withMessage: "Could not parse a parser variable declaration statement"))
}
let value =
if !value_capture.isEmpty {
value_capture[0].node.text!
} else {
""
}
guard case .Ok(let p4_type) = Parser.ParseBasicType(type: type_name) else {
return Result.Error(
Error(withMessage: "Could not parse a P4 type from \(type_name)"))
}
return switch Parser.ParseValueType(type: p4_type, withValue: value) {
case Result.Ok(let value_type):
// This scope should have an additional variable in scope.
Result.Ok(
(
VariableDeclarationStatement(
withVariable: Variable(name: variable_name, withValue: value_type, isConstant: false)),
scopes.declare(
variable: Variable(name: variable_name, withValue: value_type, isConstant: false))
))
case Result.Error(let e):
Result.Error(e)
}
}
}
public struct Parser {
static func ParseBasicType(type: String) -> Result<P4Type> {
if type == "bool" {
return .Ok(P4Boolean.create())
} else if type == "string" {
return .Ok(P4String.create())
} else if type == "int" {
return .Ok(P4Int.create())
}
return Result.Error(Error(withMessage: "Type name not recognized"))
}
static func ParseValueType(type: P4Type, withValue value: String) -> Result<P4Value> {
if type.eq(rhs: P4Boolean.create()) {
// 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.eq(rhs: P4String.create()) {
return .Ok(P4StringValue.init(withValue: value))
} else if type.eq(rhs: P4Int.create()) {
// 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 {
static func LocalElements(
node: Node, inTree tree: MutableTree, withScope scopes: Scopes
) -> Result<(EvaluatableParserStatement, Scopes)> {
let localElementsParsers: [ParseableParserStatement.Type] = [
VariableDeclarationStatement.self
]
for local_element_parser in localElementsParsers {
if case Result.Ok((.some(let parsed), let parsed_updated_scopes)) =
local_element_parser.Parse(
node: node, inTree: tree, withScope: scopes)
{
return Result.Ok((parsed, parsed_updated_scopes))
}
}
return Result.Error(
Error(
withMessage:
"Failed to parse any local elements from specified local elements: \(node.text!)")
)
}
static func Statements(
node: Node, inTree tree: MutableTree, withScope scopes: Scopes
) -> Result<(EvaluatableParserStatement, Scopes)> {
let statementParsers: [ParseableParserStatement.Type] = [
ExpressionStatement.self, VariableDeclarationStatement.self, ParserAssignmentStatement.self
]
// Iterate through statement parsers and give each one a chance.
for parser in statementParsers {
if case Result.Ok((.some(let parsed), let updatedScopes)) = parser.Parse(
node: node, inTree: tree, withScope: scopes)
{
return .Ok((parsed, updatedScopes))
}
}
return Result.Error(
Error(withMessage: "Failed to parse a statement element: \(node.text!)"))
}
static func TransitionKeysetExpression(
node: Node, inTree tree: MutableTree, withScopes scopes: Scopes
) -> Result<([KeysetExpression], Scopes)> {
guard
let transition_selection_expression_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"((keysetExpression (expression) @ks) (colon) (identifier) @next-state)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let qr = transition_selection_expression_query.execute(node: node, in: tree)
var kses: [KeysetExpression] = Array()
for expression in qr {
let next_state_name = expression.captures[1].node.text!
if case .Error(let e) = Expression.Parse(
node: expression.captures[0].node, inTree: tree, withScopes: scopes
)
.map(block: { expression in
kses.append(
KeysetExpression(
withKey: expression, withNextStateName: next_state_name))
return .Ok(expression)
}) {
return .Error(e)
}
}
return .Ok((kses, scopes))
}
static func TransitionSelectExpression(
node: Node, inTree tree: MutableTree, withScope scopes: Scopes
) -> Result<(ParserTransitionStatement?, Scopes)> {
guard
let transition_selection_expression_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(parserTransitionStatement (transition) (transitionSelectionExpression (selectExpression (select) (expression) @selector (selectBody) @body)))"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let qr = transition_selection_expression_query.execute(node: node, in: tree)
guard let query_result = qr.next() else {
return .Error(Error(withMessage: "Could not find transition select expression"))
}
let selector = query_result.captures(named: "selector")
let body = query_result.captures(named: "body")
return Expression.Parse(node: selector[0].node, inTree: tree, withScopes: scopes).map {
expression in
return
switch TransitionKeysetExpression(node: body[0].node, inTree: tree, withScopes: scopes)
{
case .Ok((let kse, let newScopes)):
Result<(ParserTransitionStatement?, Scopes)>.Ok(
(
ParserTransitionStatement(
withTransitionExpression: ParserTransitionSelectExpression(
withSelector: expression, withKeysetExpressions: kse)), newScopes
))
case .Error(let e): Result.Error(e)
}
}
}
static func TransitionStatement(
node: Node, inTree tree: MutableTree, withScope scopes: Scopes
) -> Result<(ParserTransitionStatement?, Scopes)> {
guard
let next_state_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(parserTransitionStatement (transition) (transitionSelectionExpression (identifier) @next-state))"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let qr = next_state_query.execute(node: node, in: tree)
if let next_state_result = qr.next() {
let transition_capture = next_state_result.captures(named: "next-state")
return .Ok(
(ParserTransitionStatement(withNextState: transition_capture[0].node.text!), scopes))
}
return TransitionSelectExpression(node: node, inTree: tree, withScope: scopes)
}
static func State(
node: Node, inTree tree: MutableTree, withScopes scopes: Scopes
) -> Result<(ParserState, Scopes)> {
guard
let parser_state_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(parserState (state) (identifier) @state-name (parserLocalElements ((parserLocalElement) @state-local-element (semicolon))*)* (parserStatements ((parserStatement) @state-statement (semicolon))*)* (parserTransitionStatement) @transition)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(Error(withMessage: "Could not compile the tree sitter query"))
}
let qr = parser_state_query.execute(node: node, in: tree)
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-element")
let statements_capture = parser_declaration.captures(named: "state-statement")
// TODO: Now that scopes are involved, doing this out of order will not work!
guard !state_name_capture.isEmpty,
!transition_capture.isEmpty,
let parsed_state_name = state_name_capture[0].node.text,
case .Ok((let transition_statement, (var newStateScopes))) = TransitionStatement(
node: transition_capture[0].node, inTree: tree, withScope: scopes)
else {
return Result.Error(Error(withMessage: "Could not parse a parser declaration"))
}
var parsed_les: [EvaluatableParserStatement] = Array()
var parse_err: Error? = .none
for state_le in state_le_capture {
state_le.node.enumerateChildren { node in
switch LocalElements(
node: node, inTree: tree, withScope: newStateScopes)
{
case .Ok((let le, let le_parsed_scopes)):
newStateScopes = le_parsed_scopes
parsed_les.append(le)
case .Error(let e):
parse_err = e
}
}
}
if let parse_err = parse_err {
return Result.Error(parse_err)
}
var parsed_s: [EvaluatableParserStatement] = Array()
if !statements_capture.isEmpty {
for statement in statements_capture {
statement.node.enumerateChildren { node in
switch Statements(
node: node, inTree: tree, withScope: newStateScopes)
{
case .Ok((let le, let le_parsed_scopes)):
newStateScopes = le_parsed_scopes
parsed_s.append(le)
case .Error(let e):
parse_err = e
}
}
}
}
if let parse_err = parse_err {
return Result.Error(parse_err)
}
return Result.Ok(
(
ParserState(
name: parsed_state_name, withLocalElements: parsed_les,
withStatements: parsed_s,
withTransition: transition_statement!), newStateScopes
))
}
}
static func Parser(
withName name: Identifier, node: Node, inTree tree: MutableTree, withScopes scopes: Scopes
) -> Result<(Lang.Parser, Scopes)> {
guard
let parser_state_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(parserStates) @parser-states"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(
Error(withMessage: "Could not compile the parser state tree sitter query"))
}
var parser = Lang.Parser(withName: name)
// Build a state from each one listed.
let qr = parser_state_query.execute(node: node, in: tree)
let qr_value = qr.next()!
let captures = qr_value.captures(named: "parser-states")
var error: Error? = .none
var parser_scopes = scopes
// TODO: Assert that there is only one.
captures[0].node.enumerateChildren { parser_state in
switch P4Parser.State(node: parser_state, inTree: tree, withScopes: scopes) {
case Result.Ok(let (state, new_parser_scopes)):
parser.states = parser.states.append(state: state)
parser_scopes = new_parser_scopes
case Result.Error(let e): error = e
}
}
if let error = error {
return .Error(error)
}
return Result.Ok((parser, parser_scopes))
}
public static func Program(_ source: String) -> Result<Program> {
let p = SwiftTreeSitter.Parser.init()
do {
try p.setLanguage(p4lang)
} catch {
return Result.Error(Error(withMessage: "Could not configure the P4 parser"))
}
let result = p.parse(source)
guard let tree = result,
!tree.isError(lang: p4lang),
!tree.containsMissing(lang: p4lang)
else {
return Result.Error(Error(withMessage: "Could not compile the P4 program"))
}
guard
let parser_declaration_query = try? SwiftTreeSitter.Query(
language: p4lang,
data: String(
"(parserDeclaration (parserType parser_name: (identifier) @parser-name) (parserStates) @parser-states)"
).data(using: String.Encoding.utf8)!)
else {
return Result.Error(
Error(withMessage: "Could not compile the parser declaration tree sitter query"))
}
var program = Lang.Program()
// Set up a lexical scope for parsing.
var program_scope = Scopes().enter()
let parser_qc = parser_declaration_query.execute(in: tree)
for parser_declaration in parser_qc {
switch Parser(
withName: Identifier(name: parser_declaration.nodes[0].text!),
node: parser_declaration.nodes[1], inTree: tree, withScopes: program_scope)
{
case Result.Ok((let parser, let new_program_scope)):
program.types.append(parser)
program_scope = new_program_scope
case Result.Error(let error): return Result.Error(error)
}
}
return Result.Ok(program)
}
}
+4 -4
View File
@@ -24,7 +24,7 @@ import Testing
import TreeSitter import TreeSitter
import TreeSitterP4 import TreeSitterP4
@testable import Parser @testable import P4Parser
@Test func test_simple_parser_syntax_error() async throws { @Test func test_simple_parser_syntax_error() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
@@ -37,7 +37,7 @@ import TreeSitterP4
#expect( #expect(
#RequireErrorResult( #RequireErrorResult(
Error(withMessage: "Could not compile the P4 program"), Error(withMessage: "Could not compile the P4 program"),
Parser.Program(simple_parser_declaration))) Program.Parse(simple_parser_declaration)))
} }
@Test func test_simple_parser_with_statement() async throws { @Test func test_simple_parser_with_statement() async throws {
@@ -50,7 +50,7 @@ import TreeSitterP4
}; };
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Program.Parse(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
#expect(parser.states.count() == 1) #expect(parser.states.count() == 1)
@@ -72,6 +72,6 @@ import TreeSitterP4
bool() main; bool() main;
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Program.Parse(simple_parser_declaration))
#expect(#RequireOkResult(program.find_parser(withName: Identifier(name: "main_parser")))) #expect(#RequireOkResult(program.find_parser(withName: Identifier(name: "main_parser"))))
} }
+17 -17
View File
@@ -17,15 +17,15 @@
import Common import Common
import Foundation import Foundation
import Lang import P4Lang
import Macros import Macros
import Runtime import P4Runtime
import SwiftTreeSitter import SwiftTreeSitter
import Testing import Testing
import TreeSitter import TreeSitter
import TreeSitterP4 import TreeSitterP4
@testable import Parser @testable import P4Parser
@Test func test_simple_runtime() async throws { @Test func test_simple_runtime() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
@@ -37,12 +37,12 @@ import TreeSitterP4
}; };
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Program.Parse(simple_parser_declaration))
let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
// We should be in the accept state. // We should be in the accept state.
#expect(state_result == Lang.accept) #expect(state_result == P4Lang.accept)
} }
@Test func test_simple_runtime_to_accept() async throws { @Test func test_simple_runtime_to_accept() async throws {
@@ -55,11 +55,11 @@ import TreeSitterP4
}; };
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Program.Parse(simple_parser_declaration))
let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
// We should be in the accept state. // We should be in the accept state.
#expect(state_result == Lang.reject) #expect(state_result == P4Lang.reject)
} }
@@ -73,12 +73,12 @@ import TreeSitterP4
}; };
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Program.Parse(simple_parser_declaration))
#expect( #expect(
#RequireErrorResult<ParserRuntime>( #RequireErrorResult<ParserRuntime>(
Error(withMessage: "No start state defined"), Error(withMessage: "No start state defined"),
Runtime.ParserRuntime.create(program: program))) P4Runtime.ParserRuntime.create(program: program)))
} }
@Test func test_simple_parser_with_transition_select_expression() async throws { @Test func test_simple_parser_with_transition_select_expression() async throws {
@@ -94,14 +94,14 @@ import TreeSitterP4
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Program.Parse(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1) #expect(parser.states.count() == 1)
#expect(state_result == Lang.accept) #expect(state_result == P4Lang.accept)
} }
@Test func test_simple_parser_with_transition_select_expression_to_reject() async throws { @Test func test_simple_parser_with_transition_select_expression_to_reject() async throws {
@@ -116,11 +116,11 @@ import TreeSitterP4
}; };
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Program.Parse(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run()) let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1) #expect(parser.states.count() == 1)
#expect(state_result == Lang.reject) #expect(state_result == P4Lang.reject)
} }
+22 -23
View File
@@ -17,15 +17,15 @@
import Common import Common
import Foundation import Foundation
import Lang import P4Lang
import Macros import Macros
import Runtime import P4Runtime
import SwiftTreeSitter import SwiftTreeSitter
import Testing import Testing
import TreeSitter import TreeSitter
import TreeSitterP4 import TreeSitterP4
@testable import Parser @testable import P4Parser
@Test func test_simple_local_element_variable_declaration() async throws { @Test func test_simple_local_element_variable_declaration() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
@@ -42,8 +42,8 @@ import TreeSitterP4
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Program.Parse(simple_parser_declaration))
let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, execution_result) = try! #UseOkResult(runtime.run()) let (state_result, execution_result) = try! #UseOkResult(runtime.run())
#expect(execution_result.scopes.count == 1) #expect(execution_result.scopes.count == 1)
@@ -53,7 +53,7 @@ import TreeSitterP4
} }
// We should be in the accept state. // We should be in the accept state.
#expect(state_result == Lang.reject) #expect(state_result == P4Lang.reject)
// There are two variables declared. // There are two variables declared.
#expect(scope.count == 2) #expect(scope.count == 2)
@@ -61,8 +61,8 @@ import TreeSitterP4
// Check the names/values of the variables in scope. // Check the names/values of the variables in scope.
let b = try #require(scope.lookup(identifier: Identifier(name: "b"))) let b = try #require(scope.lookup(identifier: Identifier(name: "b")))
let s = try #require(scope.lookup(identifier: Identifier(name: "s"))) let s = try #require(scope.lookup(identifier: Identifier(name: "s")))
#expect(b.value_type.eq(rhs: P4BooleanValue(withValue: false))) #expect(b.eq(rhs: P4BooleanValue(withValue: false)))
#expect(s.value_type.eq(rhs: P4StringValue(withValue: "\"testing\""))) #expect(s.eq(rhs: P4StringValue(withValue: "\"testing\"")))
} }
@Test func test_simple_scope() async throws { @Test func test_simple_scope() async throws {
@@ -84,19 +84,19 @@ import TreeSitterP4
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Program.Parse(simple_parser_declaration))
let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, execution_result) = try! #UseOkResult(runtime.run()) let (state_result, execution_result) = try! #UseOkResult(runtime.run())
#expect(state_result == Lang.accept) #expect(state_result == P4Lang.accept)
#expect(execution_result.scopes.count == 1) #expect(execution_result.scopes.count == 1)
let scope = try! #require(execution_result.scopes.current) let scope = try! #require(execution_result.scopes.current)
#expect(scope.count == 2) #expect(scope.count == 2)
let va = try #require(scope.lookup(identifier: Identifier(name: "va"))) let va = try #require(scope.lookup(identifier: Identifier(name: "va")))
let where_to = try #require(scope.lookup(identifier: Identifier(name: "where_to"))) let where_to = try #require(scope.lookup(identifier: Identifier(name: "where_to")))
#expect(where_to.value_type.eq(rhs: P4BooleanValue(withValue: false))) #expect(where_to.eq(rhs: P4BooleanValue(withValue: false)))
#expect(va.value_type.eq(rhs: P4IntValue(withValue: 5))) #expect(va.eq(rhs: P4IntValue(withValue: 5)))
} }
@Test func test_simple_scope2() async throws { @Test func test_simple_scope2() async throws {
@@ -119,23 +119,22 @@ import TreeSitterP4
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Program.Parse(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, execution_result) = try! #UseOkResult(runtime.run()) let (state_result, execution_result) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 2) #expect(parser.states.count() == 2)
#expect(state_result == Lang.reject) #expect(state_result == P4Lang.reject)
#expect(execution_result.scopes.count == 1) #expect(execution_result.scopes.count == 1)
let scope = try! #require(execution_result.scopes.current) let scope = try! #require(execution_result.scopes.current)
#expect(scope.count == 1) #expect(scope.count == 1)
let where_to = try #require(scope.lookup(identifier: Identifier(name: "where_to"))) let where_to = try #require(scope.lookup(identifier: Identifier(name: "where_to")))
#expect(where_to.value_type.eq(rhs: P4BooleanValue(withValue: false))) #expect(where_to.eq(rhs: P4BooleanValue(withValue: false)))
} }
@Test func test_simple_assignment() async throws { @Test func test_simple_assignment() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
parser main_parser() { parser main_parser() {
@@ -153,21 +152,21 @@ import TreeSitterP4
""" """
let program = try #UseOkResult(Parser.Program(simple_parser_declaration)) let program = try #UseOkResult(Program.Parse(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(Runtime.ParserRuntime.create(program: program)) let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, execution_result) = try! #UseOkResult(runtime.run()) let (state_result, execution_result) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1) #expect(parser.states.count() == 1)
#expect(state_result == Lang.accept) #expect(state_result == P4Lang.accept)
#expect(execution_result.scopes.count == 1) #expect(execution_result.scopes.count == 1)
let scope = try! #require(execution_result.scopes.current) let scope = try! #require(execution_result.scopes.current)
#expect(scope.count == 2) #expect(scope.count == 2)
let where_to = try #require(scope.lookup(identifier: Identifier(name: "where_to"))) let where_to = try #require(scope.lookup(identifier: Identifier(name: "where_to")))
#expect(where_to.value_type.eq(rhs: P4BooleanValue(withValue: false))) #expect(where_to.eq(rhs: P4BooleanValue(withValue: false)))
let where_from = try #require(scope.lookup(identifier: Identifier(name: "where_from"))) let where_from = try #require(scope.lookup(identifier: Identifier(name: "where_from")))
#expect(where_from.value_type.eq(rhs: P4StringValue(withValue: "\"there\""))) #expect(where_from.eq(rhs: P4StringValue(withValue: "\"there\"")))
} }
+66
View File
@@ -0,0 +1,66 @@
// 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 SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Parser
@Test func test_scope() async throws {
let s = LexicalScope()
let s2 = s.declare(identifier: Identifier(name: "first"), withValue: P4Int.create())
let found_first = try! #require(s2.lookup(identifier: Identifier(name: "first")))
#expect(found_first.eq(rhs: P4Int.create()))
#expect(s2.count == 1)
}
@Test func test_scope_no_set() async throws {
var ss = LexicalScopes().enter()
ss = ss.declare(identifier: Identifier(name: "first"), withValue: P4Int.create())
ss = ss.enter()
ss = ss.declare(identifier: Identifier(name: "second"), withValue: P4Boolean.create())
let found_first = try! #UseOkResult(ss.lookup(identifier: Identifier(name: "first")))
let found_second = try! #UseOkResult(ss.lookup(identifier: Identifier(name: "second")))
#expect(found_first.eq(rhs: P4Int.create()))
#expect(found_second.eq(rhs: P4Boolean.create()))
}
@Test func test_scope_set() async throws {
var ss = LexicalScopes().enter()
let id = Identifier(name: "first")
let id_type = P4Int.create()
ss = ss.declare(identifier: id, withValue: id_type)
ss = ss.enter()
ss = ss.declare(identifier: Identifier(name: "second"), withValue: P4Boolean.create())
// Change the value of `first`.
ss = ss.set(identifier: id, withValue: P4String.create())
// Verify the change!
let found = try! #UseOkResult(ss.lookup(identifier: id))
#expect(found.eq(rhs: P4String.create()))
}
+1 -1
View File
@@ -23,7 +23,7 @@ import Testing
import TreeSitter import TreeSitter
import TreeSitterP4 import TreeSitterP4
@testable import Parser @testable import P4Parser
struct NotStringConvertible {} struct NotStringConvertible {}
+117
View File
@@ -0,0 +1,117 @@
// 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 SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Parser
@Test func test_invalid_types() async throws {
for invalid_type_name in ["boo", "str", "in"] {
#expect(
#RequireErrorResult(
Error(withMessage: "Type name not recognized"),
Types.ParseBasicType(type: invalid_type_name)))
}
}
@Test func test_invalid_type_in_assignment() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
string where_to = "Testing";
where_to = true;
transition reject;
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"Failed to parse a statement element: Cannot assign value of type Boolean to where_to (with type String)"
),
Program.Parse(simple_parser_declaration)))
}
@Test func test_invalid_type_in_assignment2() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool where_to = true;
string where_from = "testing";
where_to = where_from;
transition reject;
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"Failed to parse a statement element: Cannot assign value of type String to where_to (with type Boolean)"
),
Program.Parse(simple_parser_declaration)))
}
@Test func test_invalid_type_in_declaration() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
string where_from = "testing";
bool where_to = where_from;
transition reject;
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"Failed to parse local element: Cannot initialize where_to (with type Boolean) from rvalue with type String"
),
Program.Parse(simple_parser_declaration)))
}
@Test func test_invalid_type_in_declaration2() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool where_to = true;
string where_from = where_to;
transition reject;
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"Failed to parse local element: Cannot initialize where_from (with type String) from rvalue with type Boolean"
),
Program.Parse(simple_parser_declaration)))
}