Compare commits
3 Commits
12fa43d9f9
...
a7d8fd1304
| Author | SHA1 | Date | |
|---|---|---|---|
| a7d8fd1304 | |||
| cbebcae20a | |||
| 979fa69ab8 |
@@ -1,12 +1,43 @@
|
|||||||
## P4CE: P4 Continuous Evolution
|
## 🏎️ GP4: Generalized P4
|
||||||
|
|
||||||
_P4CE_[^pronounce] is a P4 parser and runtime written in Swift that supports an _evolved_ version of P4.
|
"P4 is a high-level language for programming protocol-independent packet processors"[^1] and it is awesome. The language is robust and includes many features that make writing
|
||||||
|
|
||||||
[^pronounce]: The acronym is pronounced "p force".
|
- _packet_ parsing pipelines easy to write,
|
||||||
|
- _packet_ transformation pipelines easy to write,
|
||||||
|
- _packet_ routing pipelines easy to write.
|
||||||
|
|
||||||
### Evolved How?
|
The adjacent ecosystem is great, too: There are now myriad tools available that make it possible to write applications to configure the _packet_ parsing, transformation and routing pipelines written in P4.
|
||||||
|
|
||||||
Coming soon.
|
For example, a developer could use P4 to write code that parses raw bytes received from the network into a structured representation of headers/payloads used to transmit those bytes. A developer could use P4 to write a tool that designates the fields of the parsed packet that a network administrator could name when definining configuration for modifying (or not) a parsed packet and defining configuration about how (even _if_) to route the packet. In such a scenario, the system administrator might use a CLI and write
|
||||||
|
|
||||||
|
```console
|
||||||
|
PI CLI> table_add ipv4_lpm 10.0.0.1/24 => set_nhop 10.0.0.1 1
|
||||||
|
```
|
||||||
|
|
||||||
|
which the system running the P4 code written by the developer could read when making a routing decision. Later, if the system administrator wanted to change the way packets were routed, they could use the CLI and write
|
||||||
|
|
||||||
|
|
||||||
|
```console
|
||||||
|
PI CLI> table_add ipv4_lpm 10.0.0.1/24 => set_nhop 10.0.0.2 1
|
||||||
|
```
|
||||||
|
|
||||||
|
and the system running the P4 code written by the developer would immediately start to route _packets_ differently.
|
||||||
|
|
||||||
|
So, why is _packet_ highlighted?
|
||||||
|
|
||||||
|
|
||||||
|
Because we believe that thinking about P4 in exactly the way described above -- after dropping the word _packet_ -- makes it a perfect system for building general-purpose parsing, transformation and routing pipelines. Although there are many ways such tools could be used, we believe that a generalized P4 system would be a perfect candidate for writing online, streaming ETL pipelines (c.f., [Apache Kafka](https://kafka.apache.org/)) or log filter/classification pipelines (c.f., [Sigma](https://sigmahq.io/)).
|
||||||
|
|
||||||
|
Our goal is to build generalized P4 (GP4): The best of P4 and a little more.
|
||||||
|
|
||||||
|
Please join us!
|
||||||
|
|
||||||
|
[^1]: Pat Bosshart, Dan Daly, Glen Gibb, Martin Izzard, Nick McKeown, Jennifer Rexford, Cole Schlesinger, Dan Talayco, Amin Vahdat, George Varghese, and David Walker. 2014. P4: programming protocol-independent packet processors. SIGCOMM Comput. Commun. Rev. 44, 3 (July 2014), 87–95. https://doi.org/10.1145/2656877.2656890
|
||||||
|
|
||||||
|
### Benefits
|
||||||
|
|
||||||
|
1. Reuse the extensive existing work from the P4 community.
|
||||||
|
2. ... more coming soon.
|
||||||
|
|
||||||
### Status
|
### Status
|
||||||
|
|
||||||
@@ -85,13 +116,14 @@ We will try to maintain the following headline format for commit messages:
|
|||||||
where `<component>` is one of:
|
where `<component>` is one of:
|
||||||
|
|
||||||
1. `grammar`: For the tree-sitter-based grammar.
|
1. `grammar`: For the tree-sitter-based grammar.
|
||||||
2. `compiler`: For the Swift-based P4 compiler of tree-sitter-based-parser parsed programs into AST.
|
1. `compiler`: For the Swift-based P4 compiler of tree-sitter-based-parser parsed programs into AST.
|
||||||
3. `runtime`: For the Swift-based P4 interpreter.
|
1. `language`: For the Swift-based AST of a compiled P4 program.
|
||||||
4. `common`: For any Swift-based components common to the entire project (and macros).
|
1. `runtime`: For the Swift-based P4 interpreter.
|
||||||
5. `documentation`: For any documentation updates.
|
1. `common`: For any Swift-based components common to the entire project (and macros).
|
||||||
6. `testing`: For Swift-based tests.
|
1. `documentation`: For any documentation updates.
|
||||||
7. `cli`: For Cli components.
|
1. `testing`: For Swift-based tests.
|
||||||
7. `codegen`: For code generation components.
|
1. `cli`: For Cli components.
|
||||||
|
1. `codegen`: For code generation components.
|
||||||
|
|
||||||
where `<subcomponent>` can be more free-form and `<change>` is a pithy description of the changes in the commit.
|
where `<subcomponent>` can be more free-form and `<change>` is a pithy description of the changes in the commit.
|
||||||
|
|
||||||
|
|||||||
@@ -424,19 +424,30 @@ public class P4BooleanValue: P4DataValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum BitWidth: Equatable {
|
||||||
|
case Infinite
|
||||||
|
case Width(Int)
|
||||||
|
}
|
||||||
|
|
||||||
/// A P4 int type
|
/// A P4 int type
|
||||||
public struct P4Int: P4Type {
|
public struct P4Int: P4Type {
|
||||||
public init() {}
|
let width: BitWidth
|
||||||
|
|
||||||
|
public init(_ width: BitWidth = BitWidth.Infinite) {
|
||||||
|
self.width = width
|
||||||
|
}
|
||||||
|
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return "Int"
|
return "Int (width: \(self.width))"
|
||||||
}
|
}
|
||||||
|
|
||||||
public func eq(rhs: P4Type) -> Bool {
|
public func eq(rhs: P4Type) -> Bool {
|
||||||
return switch rhs {
|
return switch rhs {
|
||||||
case is P4Int: true
|
case let rrhs as P4Int: rrhs.width == self.width
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func def() -> P4DataValue? {
|
public func def() -> P4DataValue? {
|
||||||
return P4IntValue(withValue: 0)
|
return P4IntValue(withValue: 0)
|
||||||
}
|
}
|
||||||
@@ -444,12 +455,16 @@ public struct P4Int: P4Type {
|
|||||||
|
|
||||||
/// An instance of a P4 integer
|
/// An instance of a P4 integer
|
||||||
public class P4IntValue: P4DataValue {
|
public class P4IntValue: P4DataValue {
|
||||||
|
|
||||||
|
let int_type: P4Int
|
||||||
|
|
||||||
public func type() -> P4Type {
|
public func type() -> P4Type {
|
||||||
return P4Int()
|
return int_type
|
||||||
}
|
}
|
||||||
|
|
||||||
let value: Int
|
let value: Int
|
||||||
public init(withValue value: Int) {
|
public init(withValue value: Int, andWidth width: BitWidth = BitWidth.Infinite) {
|
||||||
|
self.int_type = P4Int(width)
|
||||||
self.value = value
|
self.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ public func Fold<T, A>(input: [T], initial: A, block: (T, A) -> A) -> A {
|
|||||||
#externalMacro(module: "Macros", type: "RequireNodesType")
|
#externalMacro(module: "Macros", type: "RequireNodesType")
|
||||||
@freestanding(codeItem) public macro SkipUnlessNodeType<N>(node: N, type: String) =
|
@freestanding(codeItem) public macro SkipUnlessNodeType<N>(node: N, type: String) =
|
||||||
#externalMacro(module: "Macros", type: "SkipUnlessNodeType")
|
#externalMacro(module: "Macros", type: "SkipUnlessNodeType")
|
||||||
|
@freestanding(codeItem) public macro SkipUnlessNodesTypes<N>(node: N, types: [String]) =
|
||||||
|
#externalMacro(module: "Macros", type: "SkipUnlessNodesTypes")
|
||||||
|
|
||||||
@freestanding(codeItem) public macro MustOr<E, N>(result: E, thing: E?, or: N) =
|
@freestanding(codeItem) public macro MustOr<E, N>(result: E, thing: E?, or: N) =
|
||||||
#externalMacro(module: "Macros", type: "MustOr")
|
#externalMacro(module: "Macros", type: "MustOr")
|
||||||
|
|||||||
@@ -229,6 +229,38 @@ public struct SkipUnlessNodeType: CodeItemMacro {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct SkipUnlessNodesTypes: CodeItemMacro {
|
||||||
|
public static func expansion(
|
||||||
|
of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext
|
||||||
|
) throws -> [CodeBlockItemSyntax] {
|
||||||
|
|
||||||
|
let arguments = node.arguments.indices
|
||||||
|
var arg_index = arguments.startIndex
|
||||||
|
|
||||||
|
let node_to_check = node.arguments[arg_index].expression
|
||||||
|
|
||||||
|
arg_index = arguments.index(after: arg_index)
|
||||||
|
guard let expected_types = node.arguments[arg_index].expression.as(ArrayExprSyntax.self) else {
|
||||||
|
throw MacroError(withMessage: "Node(s) to check must be in an array")
|
||||||
|
}
|
||||||
|
|
||||||
|
arg_index = arguments.index(after: arg_index)
|
||||||
|
|
||||||
|
let ifs = expected_types.elements.map { l in
|
||||||
|
"\(node_to_check).nodeType != \(l.expression)"
|
||||||
|
}.joined(separator: " && ")
|
||||||
|
|
||||||
|
return [
|
||||||
|
CodeBlockItemSyntax(
|
||||||
|
"""
|
||||||
|
if \(raw: ifs) {
|
||||||
|
return Result.Ok(.none)
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct MustOr: CodeItemMacro {
|
public struct MustOr: CodeItemMacro {
|
||||||
public static func expansion(
|
public static func expansion(
|
||||||
of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext
|
of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext
|
||||||
@@ -261,6 +293,7 @@ public struct MustOr: CodeItemMacro {
|
|||||||
struct P4Macros: CompilerPlugin {
|
struct P4Macros: CompilerPlugin {
|
||||||
var providingMacros: [Macro.Type] = [
|
var providingMacros: [Macro.Type] = [
|
||||||
RequireResult.self, RequireErrorResult.self, UseOkResult.self, UseErrorResult.self,
|
RequireResult.self, RequireErrorResult.self, UseOkResult.self, UseErrorResult.self,
|
||||||
RequireNodeType.self, SkipUnlessNodeType.self, RequireNodesType.self, MustOr.self,
|
RequireNodeType.self, SkipUnlessNodeType.self, SkipUnlessNodesTypes.self, RequireNodesType.self,
|
||||||
|
MustOr.self,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,9 +101,41 @@ extension P4IntValue: CompilableExpression {
|
|||||||
node: SwiftTreeSitter.Node, withContext context: CompilerContext
|
node: SwiftTreeSitter.Node, withContext context: CompilerContext
|
||||||
) -> Result<EvaluatableExpression?> {
|
) -> Result<EvaluatableExpression?> {
|
||||||
let node = node.child(at: 0)!
|
let node = node.child(at: 0)!
|
||||||
#SkipUnlessNodeType<SwiftTreeSitter.Node>(node: node, type: "integer")
|
|
||||||
if let parsed_int = Int(node.text!) {
|
#SkipUnlessNodesTypes<SwiftTreeSitter.Node>(
|
||||||
return .Ok(P4Value(P4IntValue(withValue: parsed_int)))
|
node: node, types: ["integer", "integer_elaborated"])
|
||||||
|
|
||||||
|
var bit_width: BitWidth = BitWidth.Infinite
|
||||||
|
let value_source: String
|
||||||
|
if node.nodeType == "integer_elaborated" {
|
||||||
|
let re = /([0-9]+)([ws])([\-0-9]+)/
|
||||||
|
let integer_components = node.text!.matches(of: re)
|
||||||
|
|
||||||
|
if integer_components.isEmpty || integer_components.count > 1 {
|
||||||
|
return .Error(
|
||||||
|
ErrorWithLocation(
|
||||||
|
sourceLocation: node.toSourceLocation(),
|
||||||
|
withError: "Failed to parse elaborated integer: \(node.text!)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
let width_source = "\(integer_components[0].1)"
|
||||||
|
guard let width = Int(width_source) else {
|
||||||
|
return .Error(
|
||||||
|
ErrorWithLocation(
|
||||||
|
sourceLocation: node.toSourceLocation(),
|
||||||
|
withError: "Failed to parse width from elaborated integer: \(width_source)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO: Handle signed vs. unsigned.
|
||||||
|
|
||||||
|
bit_width = BitWidth.Width(width)
|
||||||
|
value_source = "\(integer_components[0].3)"
|
||||||
|
} else {
|
||||||
|
value_source = node.text!
|
||||||
|
}
|
||||||
|
|
||||||
|
if let parsed_int = Int(value_source) {
|
||||||
|
return .Ok(P4Value(P4IntValue(withValue: parsed_int, andWidth: bit_width)))
|
||||||
} else {
|
} else {
|
||||||
return .Error(
|
return .Error(
|
||||||
ErrorWithLocation(
|
ErrorWithLocation(
|
||||||
|
|||||||
@@ -34,7 +34,42 @@ extension P4Int: CompilableType {
|
|||||||
public static func CompileType(
|
public static func CompileType(
|
||||||
type: SwiftTreeSitter.Node, withContext: CompilerContext
|
type: SwiftTreeSitter.Node, withContext: CompilerContext
|
||||||
) -> Common.Result<(any Common.P4Type)?> {
|
) -> Common.Result<(any Common.P4Type)?> {
|
||||||
return type.text == "int" ? .Ok(P4Int()) : .Ok(.none)
|
|
||||||
|
// Drill down, as appropriate.
|
||||||
|
let base_type_node = type.child(at: 0)!
|
||||||
|
#SkipUnlessNodeType<SwiftTreeSitter.Node>(
|
||||||
|
node: base_type_node, type: "baseType")
|
||||||
|
|
||||||
|
let type_node = base_type_node.child(at: 0)!
|
||||||
|
#SkipUnlessNodeType<SwiftTreeSitter.Node>(
|
||||||
|
node: type_node, type: "int_type")
|
||||||
|
|
||||||
|
var walker = Walker(node: type_node)
|
||||||
|
|
||||||
|
var int_node: Node? = .none
|
||||||
|
|
||||||
|
#MustOr(
|
||||||
|
result: int_node, thing: walker.getNext(),
|
||||||
|
or: Result<P4Type?>.Error(
|
||||||
|
ErrorWithLocation(
|
||||||
|
sourceLocation: type_node.toSourceLocation(),
|
||||||
|
withError: "Missing elements in int type declaration")))
|
||||||
|
|
||||||
|
// Move passed the keyword.
|
||||||
|
walker.next()
|
||||||
|
|
||||||
|
if let bit_width_node = walker.getNext() {
|
||||||
|
guard let bit_width = Int(bit_width_node.child(at: 1)!.text!),
|
||||||
|
bit_width != 0
|
||||||
|
else {
|
||||||
|
return .Error(
|
||||||
|
ErrorWithLocation(
|
||||||
|
sourceLocation: bit_width_node.toSourceLocation(),
|
||||||
|
withError: "Could not parse \(bit_width_node.text!) into integer"))
|
||||||
|
}
|
||||||
|
return .Ok(P4Int(BitWidth.Width(bit_width)))
|
||||||
|
}
|
||||||
|
return .Ok(P4Int())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +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
|
|
||||||
|
|
||||||
public struct AttributedP4Type {
|
|
||||||
public let type: P4Type
|
|
||||||
public let attributes: P4QualifiedType
|
|
||||||
|
|
||||||
public init(_ type: P4Type, _ attributes: P4QualifiedType) {
|
|
||||||
self.type = type
|
|
||||||
self.attributes = attributes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
import Common
|
import Common
|
||||||
import P4Lang
|
import P4Lang
|
||||||
|
import P4Protos
|
||||||
|
|
||||||
public struct Generated {
|
public struct Generated {
|
||||||
let gen: String
|
let gen: String
|
||||||
@@ -48,13 +49,12 @@ public struct CodeGenerator: LanguageVisitor {
|
|||||||
return visitor.start(node, context: generated)
|
return visitor.start(node, context: generated)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO: Can we generate these implementations somehow?
|
|
||||||
|
|
||||||
public typealias Context = Generated
|
public typealias Context = Generated
|
||||||
|
|
||||||
public func visit(
|
public func visit(
|
||||||
_ v: Program, _ c: VisitorContext<Generated>
|
_ v: Program, _ c: VisitorContext<Generated>
|
||||||
) -> Result<VisitorContext<Generated>> {
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
|
||||||
var result: Result<VisitorContext<Generated>> = Fold(
|
var result: Result<VisitorContext<Generated>> = Fold(
|
||||||
input: v.types, initial: .Ok(c.next(uc: c.getUserContext().append("[")))
|
input: v.types, initial: .Ok(c.next(uc: c.getUserContext().append("[")))
|
||||||
) { (current, acc) in
|
) { (current, acc) in
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ import P4Lang
|
|||||||
#RequireErrorResult(
|
#RequireErrorResult(
|
||||||
Error(
|
Error(
|
||||||
withMessage:
|
withMessage:
|
||||||
"{57, 10}: Failed to parse a statement element: {57, 1}: Cannot assign value with type Boolean to identifier z with type Int"
|
"{57, 10}: Failed to parse a statement element: {57, 1}: Cannot assign value with type Boolean to identifier z with type Int (width: Infinite)"
|
||||||
),
|
),
|
||||||
Program.Compile(simple_parser_declaration))
|
Program.Compile(simple_parser_declaration))
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -182,5 +182,5 @@ import TreeSitterP4
|
|||||||
|
|
||||||
let error = try #UseErrorResult(Program.Compile(simple_parser_declaration))
|
let error = try #UseErrorResult(Program.Compile(simple_parser_declaration))
|
||||||
|
|
||||||
#expect(error.msg().contains("{29, 12}: Type of expression in return statement (Boolean) is not compatible with function return type (Int)"))
|
#expect(error.msg().contains("{29, 12}: Type of expression in return statement (Boolean) is not compatible with function return type (Int (width: Infinite))"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ import TreeSitterP4
|
|||||||
#RequireErrorResult(
|
#RequireErrorResult(
|
||||||
Error(
|
Error(
|
||||||
withMessage:
|
withMessage:
|
||||||
"Error(s) parsing select cases: {81, 4}: Key expression of type Boolean is not compatible with selector type Int"
|
"Error(s) parsing select cases: {81, 4}: Key expression of type Boolean is not compatible with selector type Int (width: Infinite)"
|
||||||
),
|
),
|
||||||
Program.Compile(simple_parser_declaration)))
|
Program.Compile(simple_parser_declaration)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ import TreeSitterP4
|
|||||||
|
|
||||||
#expect(
|
#expect(
|
||||||
#RequireErrorResult<(InstantiatedParserState, ProgramExecution)>(
|
#RequireErrorResult<(InstantiatedParserState, ProgramExecution)>(
|
||||||
Error(withMessage: "Cannot call parser: Argument 1's type (Int) is incompatible with the parameter type (Boolean)"),
|
Error(withMessage: "Cannot call parser: Argument 1's type (Int (width: Infinite)) is incompatible with the parameter type (Boolean)"),
|
||||||
runtime.run(withArguments: args)))
|
runtime.run(withArguments: args)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ import TreeSitterP4
|
|||||||
#expect(
|
#expect(
|
||||||
#RequireErrorResult(
|
#RequireErrorResult(
|
||||||
Error(
|
Error(
|
||||||
withMessage: "{49, 13}: Failed to parse a statement element: {49, 8}: Cannot assign value of type Int to field yesno of type Boolean"
|
withMessage: "{49, 13}: Failed to parse a statement element: {49, 8}: Cannot assign value of type Int (width: Infinite) to field yesno of type Boolean"
|
||||||
),
|
),
|
||||||
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
|
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
|
||||||
)
|
)
|
||||||
@@ -457,7 +457,7 @@ import TreeSitterP4
|
|||||||
#expect(
|
#expect(
|
||||||
#RequireErrorResult(
|
#RequireErrorResult(
|
||||||
Error(
|
Error(
|
||||||
withMessage: "{49, 20}: Failed to parse a statement element: {49, 11}: Cannot assign value of type Boolean to field count of type Int"
|
withMessage: "{49, 20}: Failed to parse a statement element: {49, 11}: Cannot assign value of type Boolean to field count of type Int (width: Infinite)"
|
||||||
),
|
),
|
||||||
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
|
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -45,5 +45,5 @@ struct StringConvertible: CustomStringConvertible {
|
|||||||
|
|
||||||
@Test func test_result_type_p4value_convertible() async throws {
|
@Test func test_result_type_p4value_convertible() async throws {
|
||||||
let result = Result.Ok(P4Value(P4IntValue(withValue: 5)))
|
let result = Result.Ok(P4Value(P4IntValue(withValue: 5)))
|
||||||
#expect("\(result)" == "Ok: Value: 5 of Int type of type Int")
|
#expect("\(result)" == "Ok: Value: 5 of Int (width: Infinite) type of type Int (width: Infinite)")
|
||||||
}
|
}
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
import Common
|
import Common
|
||||||
import Foundation
|
import Foundation
|
||||||
import Macros
|
import Macros
|
||||||
import P4Runtime
|
|
||||||
import P4Lang
|
import P4Lang
|
||||||
|
import P4Runtime
|
||||||
import SwiftTreeSitter
|
import SwiftTreeSitter
|
||||||
import Testing
|
import Testing
|
||||||
import TreeSitter
|
import TreeSitter
|
||||||
@@ -36,13 +36,15 @@ import TreeSitterP4
|
|||||||
transition reject;
|
transition reject;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
""";
|
"""
|
||||||
|
|
||||||
let err = Program.Compile(simple_parser_declaration)
|
let err = Program.Compile(simple_parser_declaration)
|
||||||
guard case Result.Error(let e) = err else {
|
guard case Result.Error(let e) = err else {
|
||||||
assert(false, "Expected an error, but had success")
|
assert(false, "Expected an error, but had success")
|
||||||
}
|
}
|
||||||
#expect(e.msg().contains("Failed to parse a statement element: Could not parse a P4 type from \(invalid_type_name)"))
|
#expect(
|
||||||
|
e.msg().contains(
|
||||||
|
"Failed to parse a statement element: Could not parse a P4 type from \(invalid_type_name)"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +64,7 @@ import TreeSitterP4
|
|||||||
#RequireErrorResult(
|
#RequireErrorResult(
|
||||||
Error(
|
Error(
|
||||||
withMessage:
|
withMessage:
|
||||||
"{112, 16}: Failed to parse a statement element: {112, 8}: Cannot assign value with type Boolean to identifier where_to with type String"
|
"{112, 16}: Failed to parse a statement element: {112, 8}: Cannot assign value with type Boolean to identifier where_to with type String"
|
||||||
),
|
),
|
||||||
Program.Compile(simple_parser_declaration)))
|
Program.Compile(simple_parser_declaration)))
|
||||||
}
|
}
|
||||||
@@ -83,7 +85,7 @@ import TreeSitterP4
|
|||||||
#RequireErrorResult(
|
#RequireErrorResult(
|
||||||
Error(
|
Error(
|
||||||
withMessage:
|
withMessage:
|
||||||
"{114, 22}: Failed to parse a statement element: {114, 8}: Cannot assign value with type String to identifier where_to with type Boolean"
|
"{114, 22}: Failed to parse a statement element: {114, 8}: Cannot assign value with type String to identifier where_to with type Boolean"
|
||||||
),
|
),
|
||||||
Program.Compile(simple_parser_declaration)))
|
Program.Compile(simple_parser_declaration)))
|
||||||
}
|
}
|
||||||
@@ -128,6 +130,90 @@ import TreeSitterP4
|
|||||||
Program.Compile(simple_parser_declaration)))
|
Program.Compile(simple_parser_declaration)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test func test_invalid_type_in_declaration3() async throws {
|
||||||
|
let simple_parser_declaration = """
|
||||||
|
parser main_parser() {
|
||||||
|
state start {
|
||||||
|
int<5> specific_width_int = 5;
|
||||||
|
int unspecific_width_int = specific_width_int;
|
||||||
|
transition reject;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
|
||||||
|
let error = try! #UseErrorResult(Program.Compile(simple_parser_declaration))
|
||||||
|
|
||||||
|
#expect(
|
||||||
|
error.msg().contains(
|
||||||
|
"Cannot initialize specific_width_int (with type Int (width: Width(5))) from expression with type Int (width: Infinite)"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func test_valid_specific_width_int_type_in_declaration() async throws {
|
||||||
|
let simple_parser_declaration = """
|
||||||
|
parser main_parser() {
|
||||||
|
state start {
|
||||||
|
int<5> specific_width_int = 5w5;
|
||||||
|
int<5> unspecific_width_int = specific_width_int;
|
||||||
|
transition reject;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
|
||||||
|
#expect(#RequireOkResult(Program.Compile(simple_parser_declaration)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func test_expression_in_declaration_initializer_specific_width_int_type() async throws {
|
||||||
|
let simple_parser_declaration = """
|
||||||
|
parser main_parser() {
|
||||||
|
state start {
|
||||||
|
int<5> specific_width_int = 5w5;
|
||||||
|
bool where_to = 5w5 == specific_width_int == true;
|
||||||
|
transition select (where_to) {
|
||||||
|
true: accept;
|
||||||
|
false: reject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
|
||||||
|
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
||||||
|
let runtime = try #UseOkResult(
|
||||||
|
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
||||||
|
let (state_result, _) = try! #UseOkResult(runtime.run())
|
||||||
|
|
||||||
|
// 5w5 == specific_width_int == true
|
||||||
|
// true == true
|
||||||
|
// true
|
||||||
|
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func test_expression_in_declaration_initializer_specific_width_int_type2() async throws {
|
||||||
|
let simple_parser_declaration = """
|
||||||
|
parser main_parser() {
|
||||||
|
state start {
|
||||||
|
int<5> specific_width_int = 5w5;
|
||||||
|
bool where_to = 5w6 == specific_width_int == false;
|
||||||
|
transition select (where_to) {
|
||||||
|
true: accept;
|
||||||
|
false: reject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
|
||||||
|
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
||||||
|
let runtime = try #UseOkResult(
|
||||||
|
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
||||||
|
let (state_result, _) = try! #UseOkResult(runtime.run())
|
||||||
|
|
||||||
|
// 5w6 == specific_width_int == false
|
||||||
|
// false == false
|
||||||
|
// true
|
||||||
|
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test func test_expression_in_declaration_initializer() async throws {
|
@Test func test_expression_in_declaration_initializer() async throws {
|
||||||
let simple_parser_declaration = """
|
let simple_parser_declaration = """
|
||||||
parser main_parser() {
|
parser main_parser() {
|
||||||
@@ -141,7 +227,8 @@ import TreeSitterP4
|
|||||||
};
|
};
|
||||||
"""
|
"""
|
||||||
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
||||||
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
let runtime = try #UseOkResult(
|
||||||
|
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
||||||
let (state_result, _) = try! #UseOkResult(runtime.run())
|
let (state_result, _) = try! #UseOkResult(runtime.run())
|
||||||
|
|
||||||
// 5 == 5 == true
|
// 5 == 5 == true
|
||||||
@@ -163,7 +250,8 @@ import TreeSitterP4
|
|||||||
};
|
};
|
||||||
"""
|
"""
|
||||||
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
||||||
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
let runtime = try #UseOkResult(
|
||||||
|
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
||||||
let (state_result, _) = try! #UseOkResult(runtime.run())
|
let (state_result, _) = try! #UseOkResult(runtime.run())
|
||||||
|
|
||||||
// 5 == 5 == true
|
// 5 == 5 == true
|
||||||
@@ -185,7 +273,8 @@ import TreeSitterP4
|
|||||||
};
|
};
|
||||||
"""
|
"""
|
||||||
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
||||||
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
let runtime = try #UseOkResult(
|
||||||
|
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
||||||
let (state_result, _) = try! #UseOkResult(runtime.run())
|
let (state_result, _) = try! #UseOkResult(runtime.run())
|
||||||
|
|
||||||
// 6 == 5 == true
|
// 6 == 5 == true
|
||||||
@@ -207,7 +296,8 @@ import TreeSitterP4
|
|||||||
};
|
};
|
||||||
"""
|
"""
|
||||||
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
||||||
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
let runtime = try #UseOkResult(
|
||||||
|
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
||||||
let (state_result, _) = try! #UseOkResult(runtime.run())
|
let (state_result, _) = try! #UseOkResult(runtime.run())
|
||||||
|
|
||||||
// 6 == 5 == false
|
// 6 == 5 == false
|
||||||
@@ -229,7 +319,8 @@ import TreeSitterP4
|
|||||||
};
|
};
|
||||||
"""
|
"""
|
||||||
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
||||||
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
let runtime = try #UseOkResult(
|
||||||
|
P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
|
||||||
let (state_result, _) = try! #UseOkResult(runtime.run())
|
let (state_result, _) = try! #UseOkResult(runtime.run())
|
||||||
|
|
||||||
// TODO: This test should throw an error.
|
// TODO: This test should throw an error.
|
||||||
@@ -253,29 +344,32 @@ import TreeSitterP4
|
|||||||
};
|
};
|
||||||
"""
|
"""
|
||||||
var test_types = VarTypeScopes().enter()
|
var test_types = VarTypeScopes().enter()
|
||||||
test_types = test_types.declare(identifier: Identifier(name: "ta"), withValue: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))))
|
test_types = test_types.declare(
|
||||||
|
identifier: Identifier(name: "ta"),
|
||||||
|
withValue: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))))
|
||||||
#expect(
|
#expect(
|
||||||
#RequireErrorResult(
|
#RequireErrorResult(
|
||||||
Error(
|
Error(
|
||||||
withMessage:
|
withMessage:
|
||||||
"{49, 22}: Failed to parse a statement element: Cannot initialize where_to (with type Boolean) from expression with type Int"
|
"{49, 22}: Failed to parse a statement element: Cannot initialize where_to (with type Boolean) from expression with type Int (width: Infinite)"
|
||||||
),
|
),
|
||||||
Program.Compile(simple_parser_declaration, withGlobalInstances: test_types)))
|
Program.Compile(simple_parser_declaration, withGlobalInstances: test_types)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func test_simple_compiler_parser_parameters_invalid_types() async throws {
|
@Test func test_simple_compiler_parser_parameters_invalid_types() async throws {
|
||||||
let simple_parser_declaration = """
|
let simple_parser_declaration = """
|
||||||
parser main_parser(bool pmtr, string smtr, int imtr) {
|
parser main_parser(bool pmtr, string smtr, int imtr) {
|
||||||
state start {
|
state start {
|
||||||
pmtr = 1;
|
pmtr = 1;
|
||||||
transition accept;
|
transition accept;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
"""
|
"""
|
||||||
#expect(
|
#expect(
|
||||||
#RequireErrorResult(
|
#RequireErrorResult(
|
||||||
Error(
|
Error(
|
||||||
withMessage: "{85, 9}: Failed to parse a statement element: {85, 4}: Cannot assign value with type Int to identifier pmtr with type Boolean"
|
withMessage:
|
||||||
|
"{85, 9}: Failed to parse a statement element: {85, 4}: Cannot assign value with type Int (width: Infinite) to identifier pmtr with type Boolean"
|
||||||
),
|
),
|
||||||
Program.Compile(simple_parser_declaration)))
|
Program.Compile(simple_parser_declaration)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,12 @@ export default grammar({
|
|||||||
|
|
||||||
// Common - Types
|
// Common - Types
|
||||||
typeRef: $ => choice($.baseType, $.type_identifier),
|
typeRef: $ => choice($.baseType, $.type_identifier),
|
||||||
baseType: $ => choice($.bool, $.error, $.string, $.int, $.bit /* omitting "templated" types" */),
|
baseType: $ => choice($.bool, $.error, $.string, $.int_type, $.bit /* omitting "templated" types" */),
|
||||||
constructor_parameters: $ => seq('(', optional($.parameter_list), ')'),
|
constructor_parameters: $ => seq('(', optional($.parameter_list), ')'),
|
||||||
|
|
||||||
|
int_type: $ => seq($.int, optional($.bit_width)),
|
||||||
|
bit_width: $ => seq('<', $.integer, '>'),
|
||||||
|
|
||||||
// Common - Parsers
|
// Common - Parsers
|
||||||
parserType: $ => seq(optional($.annotations), $.parser, field('parser_name', $.identifier), optional($.typeParameters), $.parameters),
|
parserType: $ => seq(optional($.annotations), $.parser, field('parser_name', $.identifier), optional($.typeParameters), $.parameters),
|
||||||
|
|
||||||
@@ -120,7 +123,7 @@ export default grammar({
|
|||||||
// Expressions
|
// Expressions
|
||||||
expression: $ => choice($.grouped_expression, $.simple_expression),
|
expression: $ => choice($.grouped_expression, $.simple_expression),
|
||||||
grouped_expression: $ => seq('(', $.expression, ')'),
|
grouped_expression: $ => seq('(', $.expression, ')'),
|
||||||
simple_expression: $ => choice($.identifier, $.integer, $.booleanLiteralExpression, $.string_literal, $.binaryOperatorExpression, $.arrayAccessExpression, $.fieldAccessExpression, $.function_call), // Very limited.
|
simple_expression: $ => choice($.identifier, $.integer, $.integer_elaborated, $.booleanLiteralExpression, $.string_literal, $.binaryOperatorExpression, $.arrayAccessExpression, $.fieldAccessExpression, $.function_call), // Very limited.
|
||||||
booleanLiteralExpression: $ => choice($.true, $.false),
|
booleanLiteralExpression: $ => choice($.true, $.false),
|
||||||
selectExpression: $ => seq($.select, '(', $.expression, ')', '{', $.selectBody, '}'), // TODO: Should be expression list and not just a single expression
|
selectExpression: $ => seq($.select, '(', $.expression, ')', '{', $.selectBody, '}'), // TODO: Should be expression list and not just a single expression
|
||||||
transitionSelectionExpression: $ => choice($.identifier, $.selectExpression),
|
transitionSelectionExpression: $ => choice($.identifier, $.selectExpression),
|
||||||
@@ -211,6 +214,7 @@ export default grammar({
|
|||||||
type_identifier: $ => /[A-Za-z_]+/,
|
type_identifier: $ => /[A-Za-z_]+/,
|
||||||
string_literal: $ => /"[^"]*"/,
|
string_literal: $ => /"[^"]*"/,
|
||||||
integer: $ => /[0-9]+/,
|
integer: $ => /[0-9]+/,
|
||||||
|
integer_elaborated: $ => /[0-9]+[ws][-0-9]+/,
|
||||||
annotation_literal: $ => /@[A-Za-z_]+/,
|
annotation_literal: $ => /@[A-Za-z_]+/,
|
||||||
default_keyset: $=> '_',
|
default_keyset: $=> '_',
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ parser simple() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
=========================
|
=========================
|
||||||
Simple Declaration (int)
|
Simple Declaration (infinite-precision int)
|
||||||
=========================
|
=========================
|
||||||
parser simple() {
|
parser simple() {
|
||||||
state start {
|
state start {
|
||||||
@@ -73,7 +73,9 @@ parser simple() {
|
|||||||
(variableDeclaration
|
(variableDeclaration
|
||||||
(typeRef
|
(typeRef
|
||||||
(baseType
|
(baseType
|
||||||
(int)
|
(int_type
|
||||||
|
(int)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(identifier)
|
(identifier)
|
||||||
@@ -92,6 +94,59 @@ parser simple() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
=========================
|
||||||
|
Simple Declaration (fixed-precision int)
|
||||||
|
=========================
|
||||||
|
parser simple() {
|
||||||
|
state start {
|
||||||
|
int<55> l;
|
||||||
|
transition accept;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
---
|
||||||
|
(p4program
|
||||||
|
(declaration
|
||||||
|
(parserDeclaration
|
||||||
|
(parserType
|
||||||
|
(parser)
|
||||||
|
(identifier)
|
||||||
|
(parameters)
|
||||||
|
)
|
||||||
|
(parserStates
|
||||||
|
(parserState
|
||||||
|
(state)
|
||||||
|
(identifier)
|
||||||
|
(parserStatements
|
||||||
|
(parserStatement
|
||||||
|
(variableDeclaration
|
||||||
|
(typeRef
|
||||||
|
(baseType
|
||||||
|
(int_type
|
||||||
|
(int)
|
||||||
|
(bit_width
|
||||||
|
(integer)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(identifier)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(parserTransitionStatement
|
||||||
|
(transition)
|
||||||
|
(transitionSelectionExpression
|
||||||
|
(identifier)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
=========================
|
=========================
|
||||||
Simple Declaration (string)
|
Simple Declaration (string)
|
||||||
=========================
|
=========================
|
||||||
@@ -331,7 +386,9 @@ bool functionb(bool a, int b) {
|
|||||||
(parameter
|
(parameter
|
||||||
(typeRef
|
(typeRef
|
||||||
(baseType
|
(baseType
|
||||||
(int)
|
(int_type
|
||||||
|
(int)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(identifier)
|
(identifier)
|
||||||
@@ -393,7 +450,9 @@ bool functionb(in bool a, out int b, inout string c) {
|
|||||||
)
|
)
|
||||||
(typeRef
|
(typeRef
|
||||||
(baseType
|
(baseType
|
||||||
(int)
|
(int_type
|
||||||
|
(int)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(identifier)
|
(identifier)
|
||||||
@@ -468,7 +527,9 @@ extern bool functionb(in bool a, out int b, inout string c);
|
|||||||
)
|
)
|
||||||
(typeRef
|
(typeRef
|
||||||
(baseType
|
(baseType
|
||||||
(int)
|
(int_type
|
||||||
|
(int)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(identifier)
|
(identifier)
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ int fun() {
|
|||||||
(function_declaration
|
(function_declaration
|
||||||
(typeRef
|
(typeRef
|
||||||
(baseType
|
(baseType
|
||||||
(int)
|
(int_type
|
||||||
|
(int)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(identifier)
|
(identifier)
|
||||||
@@ -382,7 +384,9 @@ parser simple() {
|
|||||||
(variableDeclaration
|
(variableDeclaration
|
||||||
(typeRef
|
(typeRef
|
||||||
(baseType
|
(baseType
|
||||||
(int)
|
(int_type
|
||||||
|
(int)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(identifier)
|
(identifier)
|
||||||
|
|||||||
Reference in New Issue
Block a user