Compare commits

..

52 Commits

Author SHA1 Message Date
Will Hawkins ef6b07b54a Make Formatter Happy
Continuous Integration / Grammar Tests (push) Successful in 3m52s
Continuous Integration / Library Format Tests (push) Successful in 4m52s
Continuous Integration / Library Tests (push) Successful in 7m32s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-06-15 23:37:48 -04:00
Will Hawkins aa12974dd6 compiler: Flesh Out CST Visitor Framework
As a use case, use it to implement text serialization
of the CST.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-06-15 23:37:14 -04:00
Will Hawkins d22776b018 compiler: Refactor Language Element Tags
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-06-15 21:16:52 -04:00
Will Hawkins d7022725ed compiler: Refactor Type Parsers
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-06-15 21:16:23 -04:00
Will Hawkins 6c23cf7458 Refactor P4Compiler to P4Parser
Continuous Integration / Grammar Tests (push) Successful in 36s
Continuous Integration / Library Format Tests (push) Successful in 1m46s
Continuous Integration / Library Tests (push) Successful in 4m23s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-06-12 23:23:54 -04:00
Will Hawkins e17533dfc8 Make Formatter Happy
Continuous Integration / Grammar Tests (push) Successful in 36s
Continuous Integration / Library Format Tests (push) Successful in 1m19s
Continuous Integration / Library Tests (push) Successful in 3m51s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-06-12 06:34:57 -04:00
Will Hawkins fe88e447a9 compiler: Add LICENSE to Visitor.swift
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-06-12 06:31:21 -04:00
Will Hawkins 3769a78b92 testing: Add Back Source Code Tests
Continuous Integration / Grammar Tests (push) Successful in 39s
Continuous Integration / Library Format Tests (push) Failing after 1m15s
Continuous Integration / Library Tests (push) Successful in 4m12s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-06-12 06:30:21 -04:00
Will Hawkins b9ff228362 Start Rewrite
Continuous Integration / Grammar Tests (push) Successful in 3m54s
Continuous Integration / Library Format Tests (push) Failing after 4m49s
Continuous Integration / Library Tests (push) Successful in 7m40s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-06-12 06:24:53 -04:00
Will Hawkins 6908d9a91d compiler: Clean Up Warnings In Compiler
Continuous Integration / Grammar Tests (push) Successful in 37s
Continuous Integration / Library Format Tests (push) Successful in 1m47s
Continuous Integration / Library Tests (push) Successful in 4m51s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-29 17:47:05 -04:00
Will Hawkins 6882a32858 Make Formatter Happy
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-29 17:37:42 -04:00
Will Hawkins 3ee1eab2f8 compiler: Parser Compile Should Match Compile Protocol
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-29 17:37:26 -04:00
Will Hawkins d2797e1acc compiler: Use Macro to Derive CompilableStatement Implementations
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-29 17:36:44 -04:00
Will Hawkins 4f6de341cc compiler: Refactor Transition Statement Compilation
Make it follow the standard protocol(s). Unfortunately that means
that some additional information will have to be carried in
the compilation context. It seems like a decent tradeoff -- but
it may be revisited in the future.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-29 17:33:23 -04:00
Will Hawkins 297288e2b0 grammar: Update Instantiation Grammar Tests
Continuous Integration / Grammar Tests (push) Successful in 39s
Continuous Integration / Library Format Tests (push) Successful in 1m30s
Continuous Integration / Library Tests (push) Successful in 4m38s
A change to the grammar for parsing instantiations was not
accompanied by a change to the tests.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-29 17:12:38 -04:00
Will Hawkins 44e93e4cda compiler, runtime: Begin Runtime Refactor
Continuous Integration / Grammar Tests (push) Failing after 39s
Continuous Integration / Library Format Tests (push) Successful in 1m46s
Continuous Integration / Library Tests (push) Successful in 4m38s
Ultimately, the goal is to completely separate the compilation from
the runtime to make it possible to have the interpreter/evaluator
be "just another" entity that can perform meaningful work when
given a parsed GP4 program.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-29 08:41:54 -04:00
Will Hawkins 18461a9215 compiler: Remove Too-Early Instatiation Reference (3)
Continuous Integration / Library Format Tests (push) Successful in 1m16s
Continuous Integration / Library Tests (push) Successful in 4m32s
Continuous Integration / Grammar Tests (push) Successful in 38s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-27 13:39:26 -04:00
Will Hawkins ecc38096b8 compiler: Remove Too-Early Instatiation Reference (2)
Continuous Integration / Grammar Tests (push) Successful in 38s
Continuous Integration / Library Format Tests (push) Successful in 1m18s
Continuous Integration / Library Tests (push) Failing after 4m32s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-27 13:29:24 -04:00
Will Hawkins c8d4d4fc65 compiler: Remove Too-Early Instatiation Reference
Continuous Integration / Grammar Tests (push) Successful in 38s
Continuous Integration / Library Format Tests (push) Successful in 1m41s
Continuous Integration / Library Tests (push) Failing after 4m29s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-27 13:22:59 -04:00
Will Hawkins 294f76acd4 compiler: Refactor Compiler To Remove Ambiguities
Continuous Integration / Grammar Tests (push) Successful in 39s
Continuous Integration / Library Format Tests (push) Successful in 1m51s
Continuous Integration / Library Tests (push) Failing after 4m44s
There were significant overlaps in the names of data structures
between the compiler and the language that made it necessary
to litter the code with P4Lang.xxxx. This refactor removes that
requirement in most places (Parser is ambiguous wherever TreeSitter
is used -- cannot avoid that!)

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-27 12:59:29 -04:00
Will Hawkins 61d8f601e8 compiler, language, runtime: Separate Parser Type From Instances
Continuous Integration / Grammar Tests (push) Successful in 4m2s
Continuous Integration / Library Format Tests (push) Successful in 5m0s
Continuous Integration / Library Tests (push) Successful in 8m1s
In P4, parsers are considered types. Those parsers are instantiated.
The instantiated parsers are values. Previously, gp4 treated a parser
type and a parser value as identical. This PR makes that difference
clear _and_ sets the stage for the future.

TODO: Make the same distinction between control and action types and
values.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-27 05:41:23 -04:00
Will Hawkins 925f20a13b Make Formatter Happy
Continuous Integration / Grammar Tests (push) Successful in 37s
Continuous Integration / Library Format Tests (push) Successful in 1m49s
Continuous Integration / Library Tests (push) Successful in 4m50s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 20:42:50 -04:00
Will Hawkins 97a672bd6d compiler: Type Check All Binary Operators
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 20:42:35 -04:00
Will Hawkins bc51b4e280 compiler: Reworked Preprocessor To Support Better Error Messages
Can now support showing the trail of includes in an error
message.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 20:41:43 -04:00
Will Hawkins 041009a22e compiler: Track/Report Relative Paths Names
Continuous Integration / Grammar Tests (push) Successful in 37s
Continuous Integration / Library Format Tests (push) Successful in 1m22s
Continuous Integration / Library Tests (push) Successful in 4m23s
When the user gives relative path names for p4 files, report
those in error messages (etc.). The SourceManager can/does
resolve those to absolute path names.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 04:27:37 -04:00
Will Hawkins 0cb50e2e2f testing: Remove .swift-version
Continuous Integration / Library Format Tests (push) Successful in 1m12s
Continuous Integration / Library Tests (push) Failing after 4m18s
Continuous Integration / Grammar Tests (push) Successful in 40s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 03:54:36 -04:00
Will Hawkins 7fc319d9bc testing: Debug Format CI Test Failure (2)
Continuous Integration / Grammar Tests (push) Successful in 37s
Continuous Integration / Library Format Tests (push) Failing after 1m17s
Continuous Integration / Library Tests (push) Has been cancelled
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 03:51:52 -04:00
Will Hawkins 0c8b9e88cf testing: Debug Format CI Test Failure
Continuous Integration / Grammar Tests (push) Successful in 37s
Continuous Integration / Library Format Tests (push) Failing after 1m37s
Continuous Integration / Library Tests (push) Failing after 4m15s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 02:39:12 -04:00
Will Hawkins e53c32f802 compiler, cli: Support Nice Compilation Error Messages
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 02:38:47 -04:00
Will Hawkins 5845cb75cc testing: Test For Controls Using Keys From Structs
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 02:35:34 -04:00
Will Hawkins 24b0f0284a language: Check For Incorrect Order For Action Parameters
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 02:34:51 -04:00
Will Hawkins a1908cc077 Make Formatter Happy
Continuous Integration / Library Tests (push) Successful in 4m8s
Continuous Integration / Grammar Tests (push) Successful in 36s
Continuous Integration / Library Format Tests (push) Failing after 1m12s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 00:27:17 -04:00
Will Hawkins 382f3de00a testing: Use ProcessInfo As Static Variable
Continuous Integration / Grammar Tests (push) Successful in 37s
Continuous Integration / Library Format Tests (push) Failing after 1m40s
Continuous Integration / Library Tests (push) Successful in 4m12s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 00:19:05 -04:00
Will Hawkins 8f9fbb86bf testing: Cli Tests Are Now Integrated
Continuous Integration / Grammar Tests (push) Successful in 37s
Continuous Integration / Library Format Tests (push) Failing after 1m27s
Continuous Integration / Library Tests (push) Failing after 4m14s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 00:11:07 -04:00
Will Hawkins 16a798cc39 testing: Update to Latest ABI Protocol for Test Discovery
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-22 00:10:10 -04:00
Will Hawkins 017d5670c0 Make Formatter Happy
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-21 22:42:55 -04:00
Will Hawkins d60465e669 testing: Update CliTest Support.
And add tests for Cli preprocessing.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-21 22:41:00 -04:00
Will Hawkins b3ca30541a cli: Add a Preprocess Mode
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-21 22:40:14 -04:00
Will Hawkins 022dc94fde Commit To Swift Version 6.2.4
Continuous Integration / Grammar Tests (push) Successful in 38s
Continuous Integration / Library Format Tests (push) Failing after 1m20s
Continuous Integration / Library Tests (push) Failing after 4m34s
Continuous Integration / Cli Tests (push) Successful in 4m47s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-21 22:39:36 -04:00
Will Hawkins 7a2c55cc51 testing: Implement Macros For Cli Testing
Continuous Integration / Grammar Tests (push) Successful in 4m5s
Continuous Integration / Library Tests (push) Failing after 8m19s
Continuous Integration / Cli Tests (push) Successful in 6m36s
Continuous Integration / Library Format Tests (push) Successful in 5m0s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-20 17:53:31 -04:00
Will Hawkins 3cff82fd5c runtime: Remove Too-Soon Dependency on Protobuf Work
Continuous Integration / Grammar Tests (push) Successful in 4m18s
Continuous Integration / Library Format Tests (push) Successful in 7m30s
Continuous Integration / Library Tests (push) Successful in 10m36s
Continuous Integration / Cli Tests (push) Successful in 7m7s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-18 23:38:12 -04:00
Will Hawkins a7d8fd1304 grammar,compiler: Add Support For Fixed-Width Integers
Continuous Integration / Grammar Tests (push) Successful in 4m13s
Continuous Integration / Library Format Tests (push) Successful in 5m17s
Continuous Integration / Library Tests (push) Failing after 8m34s
Continuous Integration / Cli Tests (push) Failing after 4m40s
Distinguishing between signed and unsigned fixed-width integer
types must still be done.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-18 06:53:22 -04:00
Will Hawkins cbebcae20a language: Remove Attributed Type
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-18 05:06:28 -04:00
Will Hawkins 979fa69ab8 Rename Project (again) And Describe Purpose
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-18 04:01:28 -04:00
Will Hawkins 12fa43d9f9 common, codegen: Implement Visitor And Use For CodeGen
Continuous Integration / Grammar Tests (push) Successful in 4m6s
Continuous Integration / Library Format Tests (push) Successful in 5m5s
Continuous Integration / Library Tests (push) Successful in 9m0s
Continuous Integration / Cli Tests (push) Successful in 4m57s
Implement a generic visitor for components of a P4 program and use
it to start P4 code generation (according to the behavioral model).

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-15 08:14:24 -04:00
Will Hawkins f1f20e96a2 testing: Do Not Build Separately in Library Tests
Continuous Integration / Grammar Tests (push) Successful in 35s
Continuous Integration / Library Format Tests (push) Successful in 1m7s
Continuous Integration / Library Tests (push) Successful in 4m1s
Continuous Integration / Cli Tests (push) Successful in 5m47s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-11 09:06:59 -04:00
Will Hawkins 49eef16c19 testing: Add Basic Support for Cli Testing
Continuous Integration / Grammar Tests (push) Successful in 3m42s
Continuous Integration / Library Format Tests (push) Successful in 4m33s
Continuous Integration / Cli Tests (push) Successful in 4m18s
Continuous Integration / Library Tests (push) Successful in 9m39s
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-11 08:57:15 -04:00
Will Hawkins fccaf1aa92 cli: Initial _real_ Cli Work
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-11 08:56:38 -04:00
Will Hawkins 73b4f54bbe Make Formatter Happy
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-11 07:37:23 -04:00
Will Hawkins 0e2b13be93 compiler: Support Querying For Files In Preprocessed Code
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-11 07:37:23 -04:00
Will Hawkins f0f7a660a6 compiler: Add Ability to Annotate Preprocessed Source
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-11 07:37:18 -04:00
Will Hawkins a0c6b7730c documentation: Document SourceCode-related structs
Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
2026-05-11 07:22:17 -04:00
110 changed files with 5021 additions and 11718 deletions
-1
View File
@@ -39,7 +39,6 @@ jobs:
# See https://github.com/orgs/community/discussions/25742
- run: tree-sitter generate
working-directory: ./tree-sitter-p4
- run: swift build
- run: swift test
library-format-tests:
name: Library Format Tests
+4 -4
View File
@@ -1,5 +1,5 @@
{
"originHash" : "ef9c8ff40b986db3a591fe3c50d79e4fe2a9e11749d84a72fbe97babc9ba3683",
"originHash" : "87d6d60d6a77921d74305adbe82f77e088a6e06d44cb3db30aa4d25c864181fd",
"pins" : [
{
"identity" : "swift-argument-parser",
@@ -15,8 +15,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin",
"state" : {
"revision" : "3e4f133a77e644a5812911a0513aeb7288b07d06",
"version" : "1.4.5"
"revision" : "647c708be89f834fa6a6d4945442793a77ddf5b6",
"version" : "1.5.0"
}
},
{
@@ -52,7 +52,7 @@
"location" : "https://github.com/tree-sitter/swift-tree-sitter",
"state" : {
"branch" : "main",
"revision" : "769f4770fe8197e2f4ce810375e9b21398ae36d5"
"revision" : "f97df585296977d8fcaf644cbde567151d1367b8"
}
},
{
+13 -29
View File
@@ -10,24 +10,16 @@ let package = Package(
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "P4Compiler",
targets: ["P4Compiler"]
name: "P4Parser",
targets: ["P4Parser"]
),
.library(
name: "Common",
targets: ["Common"]
),
.library(
name: "P4Lang",
targets: ["P4Lang"]
),
.library(
name: "P4Runtime",
targets: ["P4Runtime"]
),
.executable(
name: "p4ce",
targets: ["Cli"]
name: "P4CodeGen",
targets: ["P4CodeGen"]
),
],
dependencies: [
@@ -37,6 +29,7 @@ let package = Package(
.package(url: "https://github.com/swiftlang/swift-syntax", from: "602.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-system", from: "1.6.1"),
//.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.27.0"),
],
targets: [
.macro(
@@ -47,15 +40,13 @@ let package = Package(
],
swiftSettings: [.enableExperimentalFeature("CodeItemMacros")]),
.target(
name: "P4Compiler",
name: "P4Parser",
dependencies: [
.product(name: "SwiftTreeSitter", package: "swift-tree-sitter"),
.product(name: "SwiftTreeSitterLayer", package: "swift-tree-sitter"),
.product(name: "TreeSitterP4", package: "tree-sitter-p4"),
.target(name: "TreeSitterExtensions"),
.target(name: "Common"),
.target(name: "P4Lang"),
.target(name: "P4Runtime"),
],
swiftSettings: [.enableExperimentalFeature("CodeItemMacros")],
),
@@ -71,32 +62,25 @@ let package = Package(
dependencies: [
"Macros",
.product(name: "SystemPackage", package: "swift-system"),
.product(name: "SwiftTreeSitter", package: "swift-tree-sitter"),
],
swiftSettings: [.enableExperimentalFeature("CodeItemMacros")],
),
.target(
name: "P4Lang",
dependencies: ["Common"]
),
.target(
name: "P4Runtime",
dependencies: ["P4Lang", "Common"]
),
.executableTarget(
name: "Cli",
name: "P4CodeGen",
dependencies: [
"Common", "P4Lang", "P4Compiler", "P4Runtime", "Macros",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
],
swiftSettings: [.enableExperimentalFeature("CodeItemMacros")],
"P4Parser", "Common",
//.product(name: "SwiftProtobuf", package: "swift-protobuf"),
]
),
.testTarget(
name: "Tests",
dependencies: [
"P4Compiler", "P4Runtime", "P4Lang", "Macros", "TreeSitterExtensions", "Common",
"P4Parser", "P4CodeGen", "Macros", "TreeSitterExtensions", "Common",
.product(name: "SystemPackage", package: "swift-system"),
],
//swiftSettings: [.enableExperimentalFeature("CodeItemMacros"), .unsafeFlags(["-Xfrontend", "-dump-macro-expansions"])],
swiftSettings: [.enableExperimentalFeature("CodeItemMacros")],
),
],
+44 -11
View File
@@ -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), 8795. https://doi.org/10.1145/2656877.2656890
### Benefits
1. Reuse the extensive existing work from the P4 community.
2. ... more coming soon.
### Status
@@ -85,12 +116,14 @@ We will try to maintain the following headline format for commit messages:
where `<component>` is one of:
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.
3. `runtime`: For the Swift-based P4 interpreter.
4. `common`: For any Swift-based components common to the entire project (and macros).
5. `documentation`: For any documentation updates.
6. `testing`: For Swift-based tests.
7. `cli`: For Cli components.
1. `compiler`: For the Swift-based P4 compiler of tree-sitter-based-parser parsed programs into AST.
1. `language`: For the Swift-based AST of a compiled P4 program.
1. `runtime`: For the Swift-based P4 interpreter.
1. `common`: For any Swift-based components common to the entire project (and macros).
1. `documentation`: For any documentation updates.
1. `testing`: For Swift-based tests.
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.
@@ -1,50 +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 Foundation
import Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
import P4Lang
import P4Compiler
let p4_program_with_control_decl = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
}
};
"""
// snippet.include
let flter = { (tipe: P4QualifiedType) -> Bool in
switch tipe.baseType(){
case let c as Control: c.name == "simple"
default: false
}
}
if case .Ok(let program) = Program.Compile(p4_program_with_control_decl) {
print(program.InstancesWithTypes(flter))
}
-44
View File
@@ -1,44 +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 Foundation
import Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
import P4Lang
import P4Compiler
let p4_program_with_struct_decl = """
struct agg {
int x;
};
"""
// snippet.include
let flter = { (tipe: P4Type) -> Bool in
switch tipe {
case let c as P4Struct: c.name == "agg"
default: false
}
}
if case .Ok(let program) = Program.Compile(p4_program_with_struct_decl) {
print(program.TypesWithTypes(flter))
}
+131 -5
View File
@@ -17,13 +17,139 @@
import ArgumentParser
import Common
import P4Compiler
import P4Runtime
import SystemPackage
@main
struct Cli: ParsableCommand {
public func run() throws {
let formatter = FormatterPlain()
let e = ErrorWithLocation(sourceLocation: SourceLocation(1, 5), withError: "Testing")
let e1 = ErrorWithLocation(sourceLocation: SourceLocation(10, 5), withError: "Oh no")
print(e.append(error: e1).format(formatter))
@Flag(help: "Disable ANSI-stylized output.") var plain: Int
static let configuration = CommandConfiguration(
abstract: "P4CE compiler, interpreter and debugger.",
subcommands: [Preprocess.self, Compile.self, CodeGen.self])
}
struct CliOptions: ParsableArguments {
@ArgumentParser.Argument(help: "File to compile.") // Have to be explicit because Common has an Argument, too!
var path: String
@Option(name: [.customShort("I")], help: "Search paths.")
var search: [String] = []
}
extension Cli {
struct Preprocess: ParsableCommand {
static let configuration = CommandConfiguration(abstract: "Compile P4CE code.")
@ParentCommand var parent: Cli
@OptionGroup var options: CliOptions
mutating func run() {
let sm = SourceManager(options.search.map { FilePath($0) })
let prep = SourceCodePreprocessor(sm)
let file = FilePath(options.path)
let formatter: any Formattable =
if parent.plain != 0 {
FormatterPlain()
} else {
FormatterAnsi()
}
let maybe_source = prep.preprocess(file)
guard case .Ok(let source) = maybe_source else {
print(ErrorWithLabel("Preprocessor Error", maybe_source.error()!).format(formatter))
return
}
print(source.getSource())
}
}
}
extension Cli {
struct Compile: ParsableCommand {
static let configuration = CommandConfiguration(abstract: "Compile P4CE code.")
@ParentCommand var parent: Cli
@OptionGroup var options: CliOptions
mutating func run() {
let sm = SourceManager(options.search.map { FilePath($0) })
let prep = SourceCodePreprocessor(sm)
let file = FilePath(options.path)
let formatter: any Formattable =
if parent.plain != 0 {
FormatterPlain()
} else {
FormatterAnsi()
}
let maybe_source = prep.preprocess(file)
guard case .Ok(let source) = maybe_source else {
print(ErrorWithLabel("Preprocessor Error", maybe_source.error()!).format(formatter))
return
}
let maybe_program = SpecialCompilers.ProgramCompiler.Compile(source.getSource())
guard case .Ok(_) = maybe_program else {
let feedback = CompilationFeedback(source, [maybe_program.error()!], formatter)
print(feedback.feedback)
return
}
print(
formatter.formatWithStyle(
"Success", Style(StyleColor.Green, [StyleFormat.Underline])))
}
}
}
extension Cli {
struct CodeGen: ParsableCommand {
static let configuration = CommandConfiguration(abstract: "Generate P4CE code.")
@ParentCommand var parent: Cli
@OptionGroup var options: CliOptions
mutating func run() {
let sm = SourceManager(options.search.map { FilePath($0) })
let prep = SourceCodePreprocessor(sm)
let file = FilePath(options.path)
let formatter: any Formattable =
if parent.plain != 0 {
FormatterPlain()
} else {
FormatterAnsi()
}
let maybe_source = prep.preprocess(file)
guard case .Ok(let source) = maybe_source else {
print(ErrorWithLabel("Preprocessor Error", maybe_source.error()!).format(formatter))
return
}
let maybe_program = SpecialCompilers.ProgramCompiler.Compile(source.getSource())
guard case .Ok(let program) = maybe_program else {
print(ErrorWithLabel("Compiler Error", maybe_source.error()!).format(formatter))
return
}
/*
let maybe_codegen = P4Runtime.CodeGenerator().codeGen(program)
guard case .Ok(let codegen) = maybe_codegen else {
let formatter = FormatterAnsi()
print(ErrorWithLabel("Code Generation Error", maybe_codegen.error()!).format(formatter))
return
}
print("\(codegen.getGeneratedCode())")
*/
}
}
}
-134
View File
@@ -1,134 +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/>.
public struct Parameter: CustomStringConvertible, Equatable {
public static func == (lhs: Parameter, rhs: Parameter) -> Bool {
return lhs.name == rhs.name && lhs.type.eq(rhs.type)
}
public var name: Identifier
public var type: P4QualifiedType
public init(
identifier: Identifier, withType type: P4QualifiedType
) {
self.name = identifier
self.type = type
}
public var description: String {
return "Parameter: \(self.name) with type \(self.type)"
}
/// Calculate whether the `argument` is compatible with this parameter.
public func compatible(_ argument: Argument) -> Bool {
let arg_type = argument.argument.type()
// If the parameter is (in)out, then the argument must be an lvalue.
if let param_direction = self.type.direction(),
param_direction == Direction.In || param_direction == Direction.InOut
{
if !(argument.argument is EvaluatableLValueExpression) {
return false
}
}
return arg_type.baseType().eq(rhs: self.type.baseType())
}
}
public struct ParameterList: CustomStringConvertible, Equatable {
public static func == (lhs: ParameterList, rhs: ParameterList) -> Bool {
if lhs.parameters.count != rhs.parameters.count {
return false
}
return 0
== zip(lhs.parameters, rhs.parameters).count { (lparam, rparam) in
return lparam != rparam
}
}
public var parameters: [Parameter]
public init() {
self.parameters = Array()
}
public init(_ parameters: [Parameter]) {
self.parameters = parameters
}
public func addParameter(_ parameter: Parameter) -> ParameterList {
return ParameterList(self.parameters + [parameter])
}
public var description: String {
let parameters = self.parameters.map { parameter in
parameter.description
}.joined(separator: ";")
return "Parameter list: \(parameters)"
}
}
public struct ArgumentList {
public let arguments: [Argument]
public init(_ arguments: [Argument] = []) {
self.arguments = arguments
}
public func compatible(_ parameters: ParameterList) -> Result<()> {
if self.arguments.count != parameters.parameters.count {
return .Error(
Error(
withMessage:
"\(self.arguments.count) arguments found but \(parameters.parameters.count) required"))
}
for (arg, param) in zip(self.arguments, parameters.parameters) {
let arg_index = arg.index
let arg_type = arg.argument.type()
if !param.compatible(arg) {
return .Error(
Error(
withMessage:
"Argument \(arg_index)'s type (\(arg_type)) is incompatible with the parameter type (\(param.type))"
))
}
}
return .Ok(())
}
public func addArgument(_ argument: Argument) -> ArgumentList {
return ArgumentList(self.arguments + [argument])
}
public func count() -> Int {
return self.arguments.count
}
}
public struct Argument {
public let index: Int
public let argument: EvaluatableExpression
public init(_ argument: EvaluatableExpression, atIndex index: Int) {
self.argument = argument
self.index = index
}
}
-28
View File
@@ -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/>.
/// A scope that resolves variable identifiers to their types.
public typealias VarTypeScope = Scope<P4QualifiedType>
/// Scopes that resolve variable identifiers to their types.
public typealias VarTypeScopes = Scopes<P4QualifiedType>
/// A scope that resolves type identifiers to their types.
public typealias TypeTypeScope = Scope<P4Type>
/// Scopes that resolve type identifiers to their types.
public typealias TypeTypeScopes = Scopes<P4Type>
+32 -1
View File
@@ -39,7 +39,38 @@ public struct Error: Errorable, Equatable, CustomStringConvertible {
}
}
extension String {
public func trimmingPostfix(_ in_: String.Element) -> String {
return String(self.reversed().drop { $0 == in_ }.reversed())
}
}
public struct ErrorWithLocation: Errorable, Equatable, CustomStringConvertible {
public func format(_ formatter: any Formattable, _ sc: SourceCode) -> String {
guard let (fp, source, prior, after) = sc.getSourceSnippet(location: self.location, context: 5)
else {
return self.format(formatter)
}
let prior_snipped = prior.trimmingPrefix(["\n"])
let after_snipped = after.prefix { $0 != "\n" }
let include_list = fp.reversed().map {
let at =
if let whence = $0.whence {
" included at position \(whence) in"
} else {
""
}
return $0.path.string + at
}.joined(separator: " ")
return formatter.formatWithStyle("Error: ", Style(StyleColor.Red))
+ "In \(include_list), there was an error: \n..." + prior_snipped
+ formatter.formatWithStyle(source, Style(.none, [StyleFormat.Underline])) + after_snipped
+ "...\n"
+ self._msg
}
public func format(_ formatter: any Formattable) -> String {
let bold_red = Style(StyleColor.Red, [StyleFormat.Bold])
let formatted_location = formatter.formatWithStyle(self.location.description, bold_red)
@@ -107,7 +138,7 @@ public struct ErrorWithLabel: Errorable {
public func format(_ formatter: any Formattable) -> String {
let green = Style(StyleColor.Green)
let formatted_label = formatter.formatWithStyle(self.label, green)
return formatted_label + self.error.format(formatter)
return formatted_label + ": " + self.error.format(formatter)
}
public init(_ label: String, _ error: any Errorable) {
-267
View File
@@ -1,267 +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/>.
public typealias ExecuteStatementResultHandlerT = (ControlFlow, ProgramExecution) -> (
ControlFlow, ProgramExecution
)
public typealias ExecuteStatementT = (EvaluatableStatement, ProgramExecution) -> (
ControlFlow, ProgramExecution
)
func CanonicalExecuteStatements(
_ statements: [EvaluatableStatement], inExecution execution: ProgramExecution,
_ executor: ExecuteStatementT
) -> (ControlFlow, ProgramExecution) {
var execution = execution
for s in statements {
// Execute the statement with the user-provided statement executor.
switch executor(s, execution) {
// And decide what to do next!
case (ControlFlow.Next, let handled_next_execution): execution = handled_next_execution
case (ControlFlow.Return(let value), let handled_next_execution):
return (ControlFlow.Return(value), handled_next_execution)
case (let handled_control_flow, let handled_next_execution):
return (handled_control_flow, handled_next_execution)
}
}
return (ControlFlow.Next, execution)
}
public struct ClassicEvaluator: ProgramExecutionEvaluator {
public func ExecuteStatements(
_ statements: [EvaluatableStatement], inExecution execution: ProgramExecution,
_ handler: ExecuteStatementResultHandlerT?
) -> (ControlFlow, ProgramExecution) {
return CanonicalExecuteStatements(statements, inExecution: execution) { statement, execution in
let (cf, value) = statement.evaluate(execution: execution)
// Apply the user-specified handler before continuing.
guard let handler = handler else {
return (cf, value)
}
return handler(cf, value)
}
}
public func EvaluateExpression(
_ expression: EvaluatableExpression, inExecution execution: ProgramExecution,
) -> (Result<P4Value>, ProgramExecution) {
return expression.evaluate(execution: execution)
}
}
public struct InterloperEvaluator: ProgramExecutionEvaluator {
var statement_interloper: StatementInterloper?
var expression_interloper: ExpressionInterloper?
public init() {}
public func getStatementInterloper() -> StatementInterloper? {
return self.statement_interloper
}
public func setStatementInterloper(
_ interloper: @escaping StatementInterloper
) -> InterloperEvaluator {
var pe = self
pe.statement_interloper = interloper
return pe
}
public func getExpressionInterloper() -> ExpressionInterloper? {
return self.expression_interloper
}
public func setExpressionInterloper(
_ interloper: @escaping ExpressionInterloper
) -> InterloperEvaluator {
var pe = self
pe.expression_interloper = interloper
return pe
}
public func ExecuteStatements(
_ statements: [EvaluatableStatement], inExecution execution: ProgramExecution,
_ handler: ExecuteStatementResultHandlerT?
) -> (ControlFlow, ProgramExecution) {
var debugger: StatementInterloper? = .none
var hasDebugInterloper = false
if let found_deb = self.getStatementInterloper() {
debugger = found_deb
hasDebugInterloper = true
}
return CanonicalExecuteStatements(statements, inExecution: execution) { statement, execution in
let (cf, value) = statement.evaluate(execution: execution)
let (handled_cf, handled_value) =
if let handler = handler {
handler(cf, value)
} else {
(cf, value)
}
if hasDebugInterloper {
debugger!(statement, handled_cf, handled_value)
}
return (handled_cf, handled_value)
}
}
public func EvaluateExpression(
_ expression: EvaluatableExpression, inExecution execution: ProgramExecution,
) -> (Result<P4Value>, ProgramExecution) {
var debugger: ExpressionInterloper? = .none
var hasDebugInterloper = false
if let found_deb = self.getExpressionInterloper() {
debugger = found_deb
hasDebugInterloper = true
}
let (result, execution) = expression.evaluate(execution: execution)
if hasDebugInterloper {
debugger!(expression, result, execution)
}
return (result, execution)
}
}
public typealias StatementInterloper = (EvaluatableStatement, ControlFlow, ProgramExecution) -> Void
public typealias ExpressionInterloper = (EvaluatableExpression, Result<P4Value>, ProgramExecution)
-> Void
open class ProgramExecution: CustomStringConvertible {
public var scopes: VarValueScopes = VarValueScopes()
var globalValues: VarValueScopes?
var error: (any Errorable)?
var debug: DebugLevel = DebugLevel.Error
public let evaluator: ProgramExecutionEvaluator
init(copy: ProgramExecution) {
self.scopes = copy.scopes
self.globalValues = copy.globalValues
self.error = copy.error
self.debug = copy.debug
self.evaluator = copy.evaluator
}
public init() {
globalValues = .none
evaluator = ClassicEvaluator()
}
public init(_ evaluator: ProgramExecutionEvaluator) {
globalValues = .none
self.evaluator = evaluator
}
open var description: String {
return "Runtime:\nScopes: \(scopes)"
}
public func hasError() -> Bool {
return self.error != nil
}
public func getError() -> (any Errorable)? {
return self.error
}
public func setError(error: any Errorable) -> ProgramExecution {
let npe = ProgramExecution(copy: self)
npe.error = error
return npe
}
public func getDebugLevel() -> DebugLevel {
return self.debug
}
public func setDebugLevel(_ dl: DebugLevel) -> ProgramExecution {
let pe = ProgramExecution(copy: self)
pe.debug = dl
return pe
}
open func isDone() -> Bool {
return false
}
open func setDone() -> ProgramExecution {
// For a bare ProgramExecution, setDone is a noop.
return self
}
public func enter_scope() -> ProgramExecution {
let new_pe = ProgramExecution(copy: self)
new_pe.scopes = new_pe.scopes.enter()
return new_pe
}
public func exit_scope() -> ProgramExecution {
let new_pe = ProgramExecution(copy: self)
new_pe.scopes = new_pe.scopes.exit()
return new_pe
}
public func replaceScopes(_ new_scopes: VarValueScopes) -> ProgramExecution {
let new_pe = ProgramExecution(copy: self)
new_pe.scopes = new_scopes
return new_pe
}
public func declare(identifier: Identifier, withValue value: P4Value) -> ProgramExecution {
let new_pe = ProgramExecution(copy: self)
let new_scopes = new_pe.scopes.declare(identifier: identifier, withValue: value)
new_pe.scopes = new_scopes
return new_pe
}
public func getGlobalValues() -> VarValueScopes {
return self.globalValues ?? VarValueScopes()
}
public func setGlobalValues(_ global_values: VarValueScopes) -> ProgramExecution {
let new_pe = ProgramExecution(copy: self)
new_pe.globalValues = global_values
return new_pe
}
}
/// A scope that resolves variable identifiers to their values.
public typealias VarValueScope = Scope<P4Value>
/// Scopes that resolves variable identifiers to their values.
public typealias VarValueScopes = Scopes<P4Value>
/// Indicate the control flow result of a particular statement.
public enum ControlFlow {
case Next
case Continue
case Break
case Return(P4Value?)
case Error
}
-22
View File
@@ -1,22 +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/>.
public protocol P4FFI {
func execute(execution: ProgramExecution) -> (ControlFlow, ProgramExecution)
func type() -> P4QualifiedType
func parameters() -> ParameterList
}
+14
View File
@@ -75,6 +75,20 @@ public struct FormatterPlain: Formattable {
}
public struct FormatterDelimited: Formattable {
let start: String
let end: String
public init(_ start: String, _ end: String) {
self.start = start
self.end = end
}
public func formatWithStyle(_ value: String, _ style: Style) -> String {
return self.start + value + self.end
}
}
public struct FormatterAnsi: Formattable {
public init() {}
@@ -424,19 +424,30 @@ public class P4BooleanValue: P4DataValue {
}
}
public enum BitWidth: Equatable {
case Infinite
case Width(Int)
}
/// A P4 int type
public struct P4Int: P4Type {
public init() {}
let width: BitWidth
public init(_ width: BitWidth = BitWidth.Infinite) {
self.width = width
}
public var description: String {
return "Int"
return "Int (width: \(self.width))"
}
public func eq(rhs: P4Type) -> Bool {
return switch rhs {
case is P4Int: true
case let rrhs as P4Int: rrhs.width == self.width
default: false
}
}
public func def() -> P4DataValue? {
return P4IntValue(withValue: 0)
}
@@ -444,12 +455,16 @@ public struct P4Int: P4Type {
/// An instance of a P4 integer
public class P4IntValue: P4DataValue {
let int_type: P4Int
public func type() -> P4Type {
return P4Int()
return int_type
}
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
}
+20 -46
View File
@@ -15,28 +15,16 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
public protocol EvaluatableExpression {
/// Evaluate an expression for a given execution
/// - Parameters
/// - execution: The execution context in which to evaluate the expression
/// - Returns: The value of expression
func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution)
func type() -> P4QualifiedType
}
public protocol EvaluatableStatement {
/// Evaluate a statement for a given execution
/// - Parameters
/// - execution: The execution context in which to evaluate the parser statement
/// - Returns: A tuple of
/// 1. Whether this statement affects control flow.
/// 2. An updated execution after evaluating the parser statement
func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution)
}
public protocol P4Type: CustomStringConvertible {
func eq(rhs: any P4Type) -> Bool
func def() -> P4DataValue?
func instantiable() -> Bool
}
extension P4Type {
public func instantiable() -> Bool {
return false
}
}
public protocol P4DataValue: CustomStringConvertible {
@@ -48,30 +36,9 @@ public protocol P4DataValue: CustomStringConvertible {
func gte(rhs: P4DataValue) -> Bool
}
public protocol EvaluatableLValueExpression: EvaluatableExpression {
func set(
to: P4Value, inScopes scopes: VarValueScopes, duringExecution execution: ProgramExecution
) -> Result<(VarValueScopes, P4Value)>
func check(to: EvaluatableExpression, inScopes scopes: VarTypeScopes) -> Result<()>
}
public protocol ProgramExecutionEvaluator {
func ExecuteStatements(
_ statements: [EvaluatableStatement], inExecution execution: ProgramExecution,
_ handler: ExecuteStatementResultHandlerT?
) -> (ControlFlow, ProgramExecution)
func ExecuteStatements(
_ statements: [EvaluatableStatement], inExecution execution: ProgramExecution
) -> (ControlFlow, ProgramExecution)
func EvaluateExpression(
_ expression: EvaluatableExpression, inExecution execution: ProgramExecution,
) -> (Result<P4Value>, ProgramExecution)
}
public protocol Errorable: CustomStringConvertible {
func format(_ formatter: Formattable) -> String
func format(_ formatter: Formattable, _ sc: SourceCode) -> String
func msg() -> String
func append(error: any Errorable) -> any Errorable
func eq(_ rhs: any Errorable) -> Bool
@@ -81,16 +48,23 @@ extension Errorable {
public func eq(_ rhs: any Errorable) -> Bool {
return self.msg() == rhs.msg()
}
public func format(_ formatter: Formattable, _ sc: SourceCode) -> String {
return self.format(formatter)
}
}
public protocol Formattable {
func formatWithStyle(_ value: String, _ style: Style) -> String
}
extension ProgramExecutionEvaluator {
public func ExecuteStatements(
_ statements: [EvaluatableStatement], inExecution execution: ProgramExecution
) -> (ControlFlow, ProgramExecution) {
return ExecuteStatements(statements, inExecution: execution, .none)
public protocol P4Statement {
}
public protocol P4Expression {
}
extension P4Expression {
}
extension P4Expression {
}
+167 -50
View File
@@ -40,17 +40,52 @@ public struct SourceLocation: Equatable, CustomStringConvertible {
}
}
/// Represent a set of directories containing P4 code that can be accessed with relative paths.
/// Represent search paths for P4 code that can be accessed with relative paths.
public struct SourceManager {
let paths: [FilePath]
public init(_ paths: [FilePath]) {
/// Create a `SourceManager`
///
/// Any relative `FilePath`s in `paths` will be absolutized
/// if a `FileManager` is given.
///
/// parameters:
/// - paths: The include paths searched for files with relative paths.
/// - fm: An optional instance of a `FileManager` that will be used to
/// convert relative paths in `paths` to absolute paths.
public init(_ paths: [FilePath], _ fm: FileManager? = .none) {
// If the user gives a file manager, we will convert relative paths
// to absolute paths. Otherwise, we do not.
guard let fm else {
self.paths = paths
return
}
// There is a file manager, so we should try to absolutize any
// relative paths
self.paths = paths.map {
if !$0.isAbsolute {
return FilePath(fm.currentDirectoryPath + "/" + $0.string).lexicallyNormalized()
}
return $0
}
}
/// Return `FilePath` of `file` in search paths.
///
/// Only if `file` is relative will the search paths be searched.
///
/// parameters:
/// - file: A file to look for in the search paths.
public func firstExisting(_ file: FilePath) -> FilePath? {
if file.isAbsolute {
return file
}
let fm = FileManager()
for path in self.paths {
let combined = path.pushing(file)
let combined = path.pushing(file).lexicallyNormalized()
if fm.fileExists(atPath: combined.string) {
return combined
}
@@ -59,18 +94,28 @@ public struct SourceManager {
}
}
/// Represent preprocessed P4 code and retain information about source filenames.
/// Represent preprocessed P4 code
///
/// The preprocessed code has metadata to recover the paths of any
/// code generated by a preprocessor directive.
public struct FileSourceLocation: Equatable, CustomStringConvertible {
let location: SourceLocation
let path: FilePath
let whence: Int?
let nested: [FileSourceLocation]
public init(
_ location: SourceLocation, _ path: FilePath, _ nested: [FileSourceLocation] = Array()
_ location: SourceLocation, _ path: FilePath, _ whence: Int? = .none,
_ nested: [FileSourceLocation] = Array()
) {
self.location = location
self.path = path
self.nested = nested
self.whence = whence
}
public func update(whence: Int) -> FileSourceLocation {
return FileSourceLocation(self.location, self.path, whence, self.nested)
}
public func getLocation() -> SourceLocation {
@@ -91,6 +136,22 @@ public struct FileSourceLocation: Equatable, CustomStringConvertible {
return "\(location)"
}).joined(separator: ",") + ")"
}
public func pathForLocation(_ location: Int) -> [FileSourceLocation]? {
let queried_location = SourceLocation(location, 1)
if !self.location.contains(queried_location) {
return .none
}
for nested in self.nested {
if nested.location.contains(queried_location) {
return [self] + nested.pathForLocation(location)!
}
}
return [self]
}
}
/// Represent preprocessed P4 code.
@@ -105,73 +166,144 @@ public struct SourceCode {
self.locations = locations
}
public func getSource() -> String {
public func getManager() -> SourceManager {
return self.manager
}
static func do_annotate(
_ contents: String, _ manager: SourceManager, _ locations: FileSourceLocation,
_ formatter: Formattable, _ style: Style
) -> String {
var result = ""
// Keep track of the start of any gap between nested locations.
var gap_start = contents.startIndex
// contents are devoid of any preceding source code, but the locations do not know that. So,
// when we use a range from locations we must adjust appropriately.
let offset = locations.location.range.lowerBound
for nested in locations.getNestedLocations() {
let nested_start = contents.index(
contents.startIndex, offsetBy: nested.location.range.lowerBound - offset)
let nested_end = contents.index(
contents.startIndex, offsetBy: nested.location.range.upperBound - offset)
// Add in any gap.
result += contents[gap_start..<nested_start]
// Handle this range (recursively)
result += do_annotate(
"\(contents[nested_start ..< nested_end])", manager, nested, formatter, style)
// Adjust where the next gap will start.
gap_start = nested_end
}
// Is there anything left?
let remainder = contents[gap_start...]
return formatter.formatWithStyle(result + remainder, style)
}
public func getSource(
annotated: Bool = false, formatter: Formattable = FormatterDelimited("<", ">"),
initialStyle: Style = Style(StyleColor.Red)
) -> String {
if annotated {
return SourceCode.do_annotate(
self.code, self.manager, self.locations, formatter, initialStyle)
}
return self.code
}
public func getManager() -> SourceManager {
return self.manager
public func getSourceSnippet(
location: SourceLocation, context: Int = 0
) -> ([FileSourceLocation], String, String, String)? {
guard let paths = self.pathForLocation(location.range.lowerBound) else {
return .none
}
let lower = String.UTF8View.Index(utf16Offset: location.range.lowerBound, in: self.code)
let upper = String.UTF8View.Index(utf16Offset: location.range.upperBound, in: self.code)
let prior_start =
if location.range.lowerBound - context >= 0 {
String.UTF8View.Index(utf16Offset: location.range.lowerBound - context, in: self.code)
} else {
String.UTF8View.Index(utf16Offset: location.range.lowerBound, in: self.code)
}
let after_end =
if location.range.upperBound + context < self.code.count {
String.UTF8View.Index(utf16Offset: location.range.upperBound + context, in: self.code)
} else {
String.UTF8View.Index(utf16Offset: location.range.upperBound, in: self.code)
}
let result = String(self.code.utf16[lower..<upper])!
let prior = String(self.code.utf16[prior_start..<lower])!
let after = String(self.code.utf16[upper...after_end])!
return (paths, result, prior, after)
}
public func getLocations() -> FileSourceLocation {
return self.locations
}
public func pathForLocation(_ location: Int) -> [FileSourceLocation]? {
return self.locations.pathForLocation(location)
}
}
func do_preprocess(
_ file: URL, _ manager: SourceManager, _ starting: Int = 0
_ file: FilePath, _ manager: SourceManager, _ starting: Int = 0
) -> Result<(FileSourceLocation, String)> {
// First (1) match group has the name of the include file.
let re = /\#include[\s]*<([^\s>]*)>/
do {
var locations: [FileSourceLocation] = Array()
var contents = try String.init(contentsOf: file, encoding: String.defaultCStringEncoding)
var changed = true
while changed {
changed = false
for match in contents.matches(of: re) {
let before = contents[..<match.range.lowerBound]
let after = contents[match.range.upperBound...]
// Determine whether there is a file with that name in the include path.
guard let included_path = manager.firstExisting(FilePath.init("\(match.1)")) else {
// First, try to find the file.
guard let included_path = manager.firstExisting(file) else {
return .Error(
Error(
withMessage:
"Could not open \(match.1) for inclusion"))
"Could not open \(file) for preprocessing"))
}
// Try to make a url from the configured file.
let included_url = URL.init(filePath: included_path.string)
let orig = try String.init(
contentsOf: URL(filePath: included_path.string), encoding: String.defaultCStringEncoding)
var expanded = ""
var oloc = orig.startIndex
for match in orig.matches(of: re) {
if oloc != match.range.lowerBound {
expanded += String(orig[oloc..<match.range.lowerBound])
}
oloc = match.range.upperBound
// By calling ourselves recursively, the include being processed will
// be _completely_ expanded (including any nested includes).
switch do_preprocess(included_url, manager, starting + before.count) {
case .Ok((let location, let expanded)):
switch do_preprocess(FilePath("\(match.1)"), manager, starting + expanded.count) {
case .Ok((let location, let recursively_expanded)):
// Recombine what was before and after the include being processed
// with the expanded text.
contents = before + expanded + after
expanded += recursively_expanded
// Remember the location (and those it has nested) that were found in
// the expanded text.
locations.append(location)
locations.append(location.update(whence: match.range.lowerBound.utf16Offset(in: orig)))
case .Error(let e):
return .Error(e)
}
}
// Only process one at a time.
changed = true
break
}
}
// Make sure that we got it all!
expanded += orig[oloc...]
return .Ok(
(
FileSourceLocation(
SourceLocation(starting..<(starting + contents.count)), FilePath(file.path()), locations),
contents
SourceLocation(starting..<(starting + expanded.count)), file, .none, locations), expanded
))
} catch (let e) {
@@ -188,22 +320,7 @@ public struct SourceCodePreprocessor {
}
public func preprocess(_ file: FilePath) -> Result<SourceCode> {
// First, decide whether the given file exists at the path the user gave.
let fm = FileManager()
let file_to_open =
if !fm.fileExists(atPath: file.string) {
self.manager.firstExisting(file)
} else {
file
}
guard let file_to_open else {
return .Error(Error(withMessage: "Could not open \(file) for preprocessing"))
}
let url = URL.init(filePath: file_to_open.string)
switch do_preprocess(url, self.manager) {
switch do_preprocess(file, self.manager) {
case .Ok((let location, let contents)):
return .Ok(SourceCode(contents, self.manager, location))
case .Error(let e): return .Error(e)
+10
View File
@@ -73,6 +73,14 @@ public func Map<T, U>(input: T, block: (T) -> U) -> U {
return block(input)
}
public func Fold<T, A>(input: [T], initial: A, block: (T, A) -> A) -> A {
var result = initial
for i in input {
result = block(i, result)
}
return result
}
@freestanding(expression) public macro RequireOkResult<T>(_: Result<T>) -> Bool =
#externalMacro(module: "Macros", type: "RequireResult")
@freestanding(expression) public macro RequireErrorResult<T>(
@@ -93,6 +101,8 @@ public func Map<T, U>(input: T, block: (T) -> U) -> U {
#externalMacro(module: "Macros", type: "RequireNodesType")
@freestanding(codeItem) public macro SkipUnlessNodeType<N>(node: N, type: String) =
#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) =
#externalMacro(module: "Macros", type: "MustOr")
+205 -1
View File
@@ -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 static func expansion(
of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext
@@ -257,10 +289,182 @@ public struct MustOr: CodeItemMacro {
}
}
public struct CliTestDeclarationMacro: PeerMacro, Sendable {
// NOTE: Taken from swift-testing.
/// Get an expression initializing an instance of ``SourceLocation`` from an
/// arbitrary syntax node.
///
/// - Parameters:
/// - node: The syntax node for which an instance of ``SourceLocation`` is
/// needed.
/// - context: The macro context in which the expression is being parsed.
///
/// - Returns: An expression value that initializes an instance of
/// ``SourceLocation`` for `node`.
static func createSourceLocationExpr(
of node: some SyntaxProtocol, context: some MacroExpansionContext
) -> ExprSyntax {
if node.isProtocol((any FreestandingMacroExpansionSyntax).self) {
// Freestanding macro expressions can just use __here()
// directly and do not need to talk to the macro context to get source
// location info.
return "Testing.SourceLocation.__here()"
}
// Get the equivalent source location in both `#fileID` and `#filePath` modes.
guard let fileIDSourceLoc: AbstractSourceLocation = context.location(of: node),
let filePathSourceLoc: AbstractSourceLocation = context.location(
of: node, at: .afterLeadingTrivia, filePathMode: .filePath)
else {
return "Testing.SourceLocation.__here()"
}
return
"Testing.SourceLocation(__uncheckedFileID: \(fileIDSourceLoc.file), filePath: \(filePathSourceLoc.file), line: \(fileIDSourceLoc.line), column: \(fileIDSourceLoc.column))"
}
/// Get an expression initializing an instance of `__SourceBounds` from two
/// arbitrary syntax nodesvalues.
///
/// - Parameters:
/// - lowerBoundNode: The syntax node representing the lower bound. The start
/// of this node (after leading trivia) is used.
/// - upperBoundNode: The syntax node representing the upper bound. The end of
/// this node (before trailing trivia) is used.
/// - context: The macro context in which the expression is being parsed.
///
/// - Returns: An expression value that initializes an instance of
/// `__SourceBounds`.
///
/// The resulting source bounds instance represents (approximately):
///
/// ```swift
/// lowerBoundNode.positionAfterSkippingLeadingTrivia ..< upperBoundNode.endPositionBeforeTrailingTrivia
/// ```
static func createSourceBoundsExpr(
from lowerBoundNode: some SyntaxProtocol, to upperBoundNode: some SyntaxProtocol,
in context: some MacroExpansionContext
) -> ExprSyntax {
let lowerBoundExpr = createSourceLocationExpr(of: lowerBoundNode, context: context)
let upperBoundExpr: ExprSyntax =
if let upperBoundSourceLoc = context.location(
of: upperBoundNode, at: .beforeTrailingTrivia, filePathMode: .fileID)
{
"(\(upperBoundSourceLoc.line), \(upperBoundSourceLoc.column))"
} else {
"(.max, .max)"
}
return
"Testing.__SourceBounds(__uncheckedLowerBound: \(lowerBoundExpr), upperBound: \(upperBoundExpr))"
}
// NOTE: End of what was taken from swift-testing
private static func doc_shrink(_ from: String) -> String {
return from.replacing(Regex(#/^.*\/\/\/[\s]+/#), with: "")
}
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
let test_name = declaration.cast(FunctionDeclSyntax.self).name
let cli_test_expected_output = node.leadingTrivia.filter({ $0.isComment }).map({
doc_shrink("\($0)")
}).joined(separator: "\\n")
let cli_test_driver_thunk_name = context.makeUniqueName("_thunk_")
let (expected_decl, expected_label) =
if cli_test_expected_output.isEmpty {
("let expected = \"\(test_name)\"", "withExpectedPath:")
} else {
("let expected = \"\(cli_test_expected_output)\"", "withExpected:")
}
let cli_test_driver_thunk: DeclSyntax = """
@Sendable private func \(cli_test_driver_thunk_name)() async throws {
\(raw: expected_decl)
_ = unsafe try await Testing.__requiringUnsafe(
Testing.__requiringTry(
Testing.__requiringAwait(swiftCliTestRunner(\(test_name), \(raw: expected_label) expected))))
}
"""
let source_bounds = createSourceBoundsExpr(from: node, to: declaration, in: context)
let cli_test_driver_generator_name = context.makeUniqueName("_generator_")
let cli_test_driver_generator: DeclSyntax = """
@Sendable private func \(cli_test_driver_generator_name)() async -> Testing.Test {
return .__function(
named: "\(test_name)",
in: nil as Swift.Never.Type?,
xcTestCompatibleSelector: Testing.__xcTestCompatibleSelector("\(test_name)"),
traits: [],
sourceBounds: \(source_bounds),
parameters: [],
testFunction: \(cli_test_driver_thunk_name)
)
}
"""
#if os(macOS)
let section = "__DATA_CONST,__swift5_tests"
#else
let section = "swift5_tests"
#endif
let cli_test_driver_content_record_name = context.makeUniqueName("testContentRecord")
let cli_test_driver_cr: DeclSyntax = """
@section("\(raw: section)")
@used
private nonisolated let \(cli_test_driver_content_record_name): Testing.__TestContentRecord = (
0x7465_7374, /* indicate a test */
0,
{ outValue, type, _, _ in
Testing.Test.__store(\(cli_test_driver_generator_name), into: outValue, asTypeAt: type)
},
0,
0
)
"""
return [
cli_test_driver_thunk, cli_test_driver_generator, cli_test_driver_cr,
]
}
}
public enum DeriveParsableStatement: MemberMacro {
public static func expansion(
of: AttributeSyntax, providingMembersOf type: some DeclGroupSyntax, conformingTo: [TypeSyntax],
in: some MacroExpansionContext
) throws -> [DeclSyntax] {
let implementation = DeclSyntax(
"""
public static func ParseStatement(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Statement> {
return switch Parse(node: node, withContext: context) {
case .Ok(let res): .Ok(res)
case .Error(let e): .Error(e)
}
}
""")
return [implementation]
}
}
@main
struct P4Macros: CompilerPlugin {
var providingMacros: [Macro.Type] = [
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, CliTestDeclarationMacro.self, DeriveParsableStatement.self,
]
}
+256
View File
@@ -0,0 +1,256 @@
// 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 P4Parser
public struct CSTTextSerializer {
public init() {}
}
public struct CSTTextSerializerContext {
public let serialized: String
public let indents: Int
public init(_ serialized: String = "", _ indents: Int = 0) {
self.serialized = serialized
self.indents = indents
}
static func produceIndent(_ indent: Int, _ marker: String) -> String {
return repeatElement(marker, count: indent).joined()
}
public func append(_ a: String) -> CSTTextSerializerContext {
return CSTTextSerializerContext(
self.serialized + Self.produceIndent(self.indents, "\t") + a + "\n", self.indents)
}
public func indent() -> CSTTextSerializerContext {
return CSTTextSerializerContext(self.serialized, self.indents + 1)
}
public func unindent() -> CSTTextSerializerContext {
return CSTTextSerializerContext(self.serialized, self.indents - 1)
}
}
extension CSTTextSerializer: CSTVisitor<CSTTextSerializerContext> {
public func visit(
node: P4Parser.CST.KeysetExpression, driver: P4Parser.CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
var context = context.append("Keyset Expression:").indent()
return .Ok(context.unindent())
if case CST.KeysetExpression.Value(let x) = node {
switch driver.visit(x, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
}
return .Ok(context.unindent())
}
public func visit(
node: P4Parser.CST.SelectCaseExpression, driver: P4Parser.CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
var context = context.append("Case Expression:").indent()
switch driver.visit(node.key, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
context = context.append("Next State:").indent()
switch driver.visit(node.next_state_identifier, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
context = context.unindent()
return .Ok(context.unindent())
}
public func visit(
node: P4Parser.CST.SelectExpression, driver: P4Parser.CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
var context = context.append("Select Expression:").indent()
context = context.append("Selector:").indent()
switch driver.visit(node.selector, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
context = context.unindent()
context = context.append("Case Expressions:").indent()
for ce in node.case_expressions {
switch driver.visit(ce, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
}
context = context.unindent()
return .Ok(context.unindent())
}
public func visit(
node: P4Parser.CST.Statements, driver: P4Parser.CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
var context = context
for s in node.statements {
switch driver.visit(s, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
}
return .Ok(context)
}
public func visit(
node: P4Parser.CST.ExpressionStatement, driver: P4Parser.CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
var context = context.append("Expression Statement:").indent()
switch driver.visit(node.expression, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e):
return .Error(e)
}
return .Ok(context.unindent())
}
public func visit(
node: P4Parser.CST.Control, driver: P4Parser.CSTVisitorDriver, context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
return .Ok(context.append("Control Declaration"))
}
public func visit(
node: P4Parser.CST.ExternDeclaration, driver: P4Parser.CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
return .Ok(context.append("Extern Declaration"))
}
public func visit(
node: P4Parser.CST.FunctionDeclaration, driver: P4Parser.CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
return .Ok(context.append("Function Declaration"))
}
public func visit(
node: P4Parser.CST.StructDeclaration, driver: P4Parser.CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
return .Ok(context.append("Struct Declaration"))
}
public func visit(
node: P4Parser.CST.VariableDeclarationStatement, driver: P4Parser.CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
return .Ok(context.append("Variable Declaration Statement"))
}
public func visit(
node: CST.BinaryOperatorExpression, driver: CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
return .Ok(context.append("Binary Operator Expression"))
}
public func visit(
node: CST.Literal, driver: CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
return .Ok(context.append("Literal Expression"))
}
public func visit(
node: CST.Identifier, driver: CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
return .Ok(context.append("Identifier: \(node.id)"))
}
public func visit(
node: CST.Parser, driver: CSTVisitorDriver, context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
var context = context.append("Parser Expression")
context = context.indent()
for s in node.states.states {
switch driver.visit(s, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
}
return .Ok(context.unindent())
}
public func visit(
node: CST.ParserStateDirectTransition, driver: CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
var context = context.append("State: Direct Transition").indent()
context = context.append("Statements:")
context = context.indent()
if let statements = node.statements {
switch driver.visit(statements, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
}
context = context.unindent()
context = context.append("Next State:").indent()
switch driver.visit(node.next_state_identifier!, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
context = context.unindent()
return .Ok(context.unindent())
}
public func visit(
node: CST.ParserStateNoTransition, driver: CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
return .Ok(context.append("State: No Transition"))
}
public func visit(
node: CST.ParserStateSelectTransition, driver: CSTVisitorDriver,
context: CSTTextSerializerContext
) -> Common.Result<CSTTextSerializerContext> {
var context = context.append("State: Select Transition").indent()
if let statements = node.statements {
context = context.indent()
switch driver.visit(statements, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
context = context.unindent()
}
switch driver.visit(node.te, visitor: self, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
return .Ok(context.unindent())
}
}
-389
View File
@@ -1,389 +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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
func parameter_list_compiler(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(ParameterList, CompilerContext)> {
var walker = Walker(node: node)
var current_node: Node? = .none
if node.text == ")" {
// There are no parameters!
return Result.Ok((ParameterList([]), context))
}
#RequireNodeType<Node, (ParameterList, CompilerContext)>(
node: node, type: "parameter_list", nice_type_name: "Parameter List")
var parameters: ParameterList = ParameterList([])
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(ParameterList, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing parameter list component")))
if current_node?.nodeType == "parameter_list" {
switch parameter_list_compiler(node: current_node!, withContext: context) {
case .Ok(let (ps, _)):
parameters = ps
case .Error(let e): return Result.Error(e)
}
walker.next()
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(ParameterList, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing parameter list component")))
// If this is a ')', we are done.
if current_node?.text == ")" {
return Result.Ok((parameters, context))
}
// If this is a comma, we skip it!
if current_node?.text == "," {
walker.next()
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(ParameterList, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing parameter list component")))
// Otherwise, there should be one parameter left!
switch Parameter.Compile(node: current_node!, withContext: context) {
case .Ok(let (parsed_parameter, updated_context)):
return Result.Ok((parameters.addParameter(parsed_parameter), updated_context))
case .Error(let e): return Result.Error(e)
}
}
extension ParameterList: Compilable {
public typealias T = ParameterList
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(ParameterList, CompilerContext)> {
let parameter_node = node
#RequireNodeType<Node, (ParameterList, CompilerContext)>(
node: parameter_node, type: "parameters", nice_type_name: "Parameters")
var walker = Walker(node: parameter_node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(ParameterList, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing '(' in parameter list component")))
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(ParameterList, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing parameter list component")))
return parameter_list_compiler(node: current_node!, withContext: context)
}
}
extension Direction: Compilable {
public typealias T = Direction
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(Direction, CompilerContext)> {
let direction_node = node
#RequireNodeType<Node, (Direction, CompilerContext)>(
node: direction_node, type: "direction", nice_type_name: "direction")
let directions = [
"in": Direction.In,
"out": Direction.Out,
"inout": Direction.InOut,
]
guard let parsed_direction = directions[direction_node.text!] else {
return .Error(
ErrorWithLocation(
sourceLocation: direction_node.toSourceLocation(),
withError: "\(direction_node.text!) is not a valid direction"))
}
return .Ok((parsed_direction, context))
}
}
extension Parameter: Compilable {
public typealias T = Parameter
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(Parameter, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
node: node, type: "parameter", nice_type_name: "parameter")
var walker = Walker(node: node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Parameter, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing parameter declaration component")))
// Annotation?
if current_node!.nodeType == "annotations" {
return .Error(
ErrorWithLocation(
sourceLocation: current_node!.toSourceLocation(),
withError: "Annotations in parameter declarations are not yet handled"))
// Will increment indexes here.
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Parameter, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing parameter declaration component")))
var direction: Direction? = .none
// Direction?
if current_node!.nodeType == "direction" {
let maybe_parsed_direction = Direction.Compile(node: current_node!, withContext: context)
guard case .Ok((let parsed_direction, _)) = maybe_parsed_direction else {
return .Error(maybe_parsed_direction.error()!)
}
direction = parsed_direction
walker.next()
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Parameter, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing parameter declaration component")))
if current_node!.nodeType != "typeRef" {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find type name for parameter declaration"))
}
guard
case .Ok(let parameter_type) = Types.CompileType(type: current_node!, withContext: context)
else {
return Result.Error(
Error(withMessage: "Could not parse a P4 type from \(current_node!.text!)"))
}
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Parameter, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing parameter declaration component")))
if current_node!.nodeType != "identifier" {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find identifier for parameter statement"))
}
guard
case .Ok(let parameter_name) = Identifier.Compile(node: current_node!, withContext: context)
else {
return Result.Error(
Error(withMessage: "Could not parse a parameter name from \(current_node!.text!)"))
}
return Result.Ok(
(
Parameter(
identifier: parameter_name,
withType: direction != nil
? parameter_type.update(addAttribute: P4TypeQualifier.Direction(direction!))
: parameter_type),
context
))
}
}
func argument_list_compiler(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(ArgumentList, CompilerContext)> {
var walker = Walker(node: node)
var current_node: Node? = .none
if node.text == ")" {
// There are no arguments!
return Result.Ok((ArgumentList([]), context))
}
#RequireNodeType<Node, (ArgumentList, CompilerContext)>(
node: node, type: "argument_list", nice_type_name: "argument List")
var arguments: ArgumentList = ArgumentList([])
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(ArgumentList, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing argument list component")))
if current_node?.nodeType == "argument_list" {
switch argument_list_compiler(node: current_node!, withContext: context) {
case .Ok(let (ps, _)):
arguments = ps
case .Error(let e): return Result.Error(e)
}
walker.next()
}
// We may have moved nodes, check/reset current_node.
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(ArgumentList, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing argument list component")))
// If this is a ')', we are done.
if current_node?.text == ")" {
return Result.Ok((arguments, context))
}
// If this is a comma, we skip it!
if current_node?.text == "," {
walker.next()
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(ArgumentList, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing argument list component")))
// Otherwise, there should be one argument left!
switch Argument.Compile(node: current_node!, withContext: context) {
case .Ok(let (ce, updated_context)):
return Result.Ok(
(arguments.addArgument(Argument(ce, atIndex: arguments.count() + 1)), updated_context))
case .Error(let e): return Result.Error(e)
}
}
extension ArgumentList: Compilable {
public typealias T = ArgumentList
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(ArgumentList, CompilerContext)> {
let argument_node = node
#RequireNodeType<Node, (ArgumentList, CompilerContext)>(
node: argument_node, type: "arguments", nice_type_name: "arguments")
var walker = Walker(node: argument_node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(ArgumentList, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing '(' in argument list component")))
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(ArgumentList, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing argument list component")))
return argument_list_compiler(node: current_node!, withContext: context)
}
}
extension Argument: Compilable {
public typealias T = EvaluatableExpression
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(EvaluatableExpression, CompilerContext)> {
let argument_node = node
#RequireNodeType<Node, (EvaluatableExpression, CompilerContext)>(
node: argument_node, type: "argument", nice_type_name: "argument")
let expression_node = node.child(at: 0)!
return switch Expression.Compile(node: expression_node, withContext: context) {
case .Ok(let compiled_expression): .Ok((compiled_expression, context))
case .Error(let e): .Error(e)
}
}
}
func ContainsInvalidStatements(
statement: EvaluatableStatement, invalids: [EvaluatableStatement.Type]
) -> Bool {
for es in invalids {
if type(of: statement) == es {
return true
}
}
return false
}
func ContainsInvalidStatements(block: BlockStatement, invalids: [EvaluatableStatement.Type]) -> Bool
{
return block.statements.contains { statement in
for es in invalids {
if type(of: statement) == es {
return true
}
}
return false
}
}
extension Node {
public func toSourceLocation() -> SourceLocation {
return SourceLocation(self.range.location, self.range.length)
}
}
-158
View File
@@ -1,158 +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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
let p4lang = Language(tree_sitter_p4())
public func ConfigureP4Parser() -> Result<SwiftTreeSitter.Parser> {
let p = SwiftTreeSitter.Parser.init()
do {
try p.setLanguage(p4lang)
} catch {
return Result.Error(Error(withMessage: "Could not configure the P4 parser"))
}
return .Ok(p)
}
/// Context for compilation
///
/// It contains (at least) three important pieces of information:
/// 1. Instances: A ``VarTypeScopes`` that contains information about instantiated objects
/// (and their types) in scope
/// 1. Types: A ``TypeTypeScopes`` that contains information about declared types in scope.
/// 1. Expected Type: In certain situations, to typecheck an element of a P4 program requires
/// knowledge of an expected type. For instance, when compiling a return statement, the
/// compiler must know the return type of the function to type check.
public struct CompilerContext {
let instances: VarTypeScopes
let types: TypeTypeScopes
let externs: TypeTypeScopes
let ffis: [P4FFI]
let expected_type: P4QualifiedType?
let extern_context: Bool
public init() {
instances = VarTypeScopes().enter()
types = TypeTypeScopes().enter()
externs = TypeTypeScopes().enter()
expected_type = .none
extern_context = false
ffis = Array()
}
public init(withInstances _instances: VarTypeScopes, withTypes _types: TypeTypeScopes) {
instances = _instances
types = _types
externs = TypeTypeScopes().enter()
expected_type = .none
extern_context = false
ffis = Array()
}
public init(
withInstances _instances: VarTypeScopes, withTypes _types: TypeTypeScopes,
withExpectation expectation: P4QualifiedType?, withExtern extern: Bool,
withExterns externs: TypeTypeScopes, withFFIs foreigns: [P4FFI]
) {
instances = _instances
types = _types
expected_type = expectation
extern_context = extern
self.externs = externs
ffis = foreigns
}
/// Update a compiler context
///
/// Create a new compiler context based on the current but new instances.
///
/// - Parameter instances: a ``VarTypeScopes`` with the updated instances for the newly created compiler context.
/// - Returns: A new compiler context based on the current but new instances.
public func update(newInstances instances: VarTypeScopes) -> CompilerContext {
return CompilerContext(
withInstances: instances, withTypes: self.types, withExpectation: self.expected_type,
withExtern: self.extern_context, withExterns: self.externs, withFFIs: self.ffis)
}
/// Update a compiler context
///
/// Create a new compiler context based on the current but new types.
///
/// - Parameter types: a ``TypeTypeScopes`` with the updated types for the newly created compiler context.
/// - Returns: A new compiler context based on the current but new types.
public func update(newTypes types: TypeTypeScopes) -> CompilerContext {
return CompilerContext(
withInstances: self.instances, withTypes: types, withExpectation: self.expected_type,
withExtern: self.extern_context, withExterns: self.externs, withFFIs: self.ffis)
}
/// Update a compiler context
///
/// Create a new compiler context based on the current but new expected type.
///
/// - Parameter expectation: a ``P4Type?`` to (re)set the type the compiler is expecting.
/// - Returns: A new compiler context based on the current but new expected type.
public func update(newExpectation expectation: P4QualifiedType?) -> CompilerContext {
return CompilerContext(
withInstances: self.instances, withTypes: self.types, withExpectation: expectation,
withExtern: self.extern_context, withExterns: self.externs, withFFIs: self.ffis)
}
/// Update a compiler context
///
/// Create a new compiler context based on the current but new extern context value.
///
/// - Parameter extern: a ``Bool`` to (re)set whether the compiler is compiling in an extern context.
/// - Returns: A new compiler context based on the current but new extern context value.
public func update(newExtern extern: Bool) -> CompilerContext {
return CompilerContext(
withInstances: self.instances, withTypes: self.types, withExpectation: self.expected_type,
withExtern: extern, withExterns: self.externs, withFFIs: self.ffis)
}
/// Update a compiler context
///
/// Create a new compiler context based on the current but new externs.
///
/// - Parameter externs: a ``TypeTypeScopes`` to (re)set the list of extern-al declarations.
/// - Returns: A new compiler context based on the current but new list of external-al declarations.
public func update(newExterns externs: TypeTypeScopes) -> CompilerContext {
return CompilerContext(
withInstances: self.instances, withTypes: self.types, withExpectation: self.expected_type,
withExtern: self.extern_context, withExterns: externs, withFFIs: self.ffis)
}
/// Update a compiler context
///
/// Create a new compiler context based on the current but new FFIs.
///
/// - Parameter foreigns: an array of ``P4FFI`` to (re)set the list of foreign functions.
/// - Returns: A new compiler context based on the current but with a new list of foreign functions.
public func update(newFFIs foreigns: [P4FFI]) -> CompilerContext {
return CompilerContext(
withInstances: self.instances, withTypes: self.types, withExpectation: self.expected_type,
withExtern: self.extern_context, withExterns: externs, withFFIs: foreigns)
}
}
-998
View File
@@ -1,998 +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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
extension Declaration: CompilableDeclaration {
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(Declaration, CompilerContext)?> {
let declaration_compilers: [String: CompilableDeclaration.Type] = [
"function_declaration": FunctionDeclaration.self,
"control_declaration": Control.self,
"type_declaration": P4Struct.self,
/// ASSUME: Type declarations are struct declarations.
"extern_declaration": ExternDeclaration.self,
]
guard let declaration_compiler = declaration_compilers[node.nodeType!] else {
return .Ok(.none)
}
return declaration_compiler.Compile(node: node, withContext: context)
}
}
extension FunctionDeclaration: CompilableDeclaration {
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(Declaration, CompilerContext)?> {
let function_declaration_node = node
#RequireNodeType<Node, (ParameterList, CompilerContext)>(
node: function_declaration_node, type: "function_declaration",
nice_type_name: "Function Declaration")
var walker = Walker(node: function_declaration_node)
var context = context
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: function_declaration_node.toSourceLocation(),
withError: "Missing function declaration component")))
let maybe_function_type = Types.CompileType(type: current_node!, withContext: context)
guard case .Ok(let function_type) = maybe_function_type else {
return .Error(maybe_function_type.error()!)
}
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: function_declaration_node.toSourceLocation(),
withError: "Missing function declaration component")))
let maybe_function_name = Identifier.Compile(node: current_node!, withContext: context)
guard case .Ok(let function_name) = maybe_function_name else {
return .Error(maybe_function_name.error()!)
}
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: function_declaration_node.toSourceLocation(),
withError: "Missing function declaration component")))
let maybe_function_parameters = ParameterList.Compile(node: current_node!, withContext: context)
guard case .Ok((let function_parameters, let updated_context)) = maybe_function_parameters
else {
return .Error(maybe_function_parameters.error()!)
}
context = updated_context
var function_body: BlockStatement? = .none
walker.next()
if let body = walker.getNext() {
// Add the parameters into scope.
var function_scope = context.instances.enter()
for parameter in function_parameters.parameters {
function_scope = function_scope.declare(
identifier: parameter.name, withValue: parameter.type)
}
let maybe_function_body = BlockStatement.Compile(
node: body,
withContext: context.update(newInstances: function_scope).update(
newExpectation: function_type))
guard case .Ok((let parsed_function_body, _)) = maybe_function_body else {
return .Error(maybe_function_body.error()!)
}
function_body = (parsed_function_body as! BlockStatement)
} else {
// If we are in an extern context, no body is okay!
if !context.extern_context {
return Result.Error(
ErrorWithLocation(
sourceLocation: function_declaration_node.toSourceLocation(),
withError: "Missing function declaration component"))
}
}
let function_declaration = Declaration(
TypedIdentifier(
id: function_name,
withType: P4QualifiedType(
FunctionDeclaration(
named: function_name, ofType: function_type, withParameters: function_parameters,
withBody: function_body))))
// Do not use the updated context returned by parsing the body
// and do not use the function_scope, either.
// And, do not update the context if we are compiling in an
// extern context.
return .Ok(
(
function_declaration,
context.extern_context
? context
: context.update(
newTypes: context.types.declare(
identifier: function_name, withValue: function_declaration.identifier.type.baseType())
)
))
}
}
extension P4Struct: CompilableDeclaration {
static public func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(Declaration, CompilerContext)?> {
let struct_declaration_node = node.child(at: 0)!
var walker = Walker(node: struct_declaration_node)
var currentNode: Node? = .none
#SkipUnlessNodeType(node: struct_declaration_node, type: "struct_declaration")
// Skip the keyword struct
walker.next()
#MustOr(
result: currentNode, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: struct_declaration_node.toSourceLocation(),
withError: "Missing function declaration component")))
// The name of the struct type.
let maybe_struct_identifier = Identifier.Compile(
node: currentNode!, withContext: context)
guard case Result.Ok(let struct_identifier) = maybe_struct_identifier else {
return Result.Error(maybe_struct_identifier.error()!)
}
walker.next()
// Skip the '{'
walker.next()
#MustOr(
result: currentNode, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: struct_declaration_node.toSourceLocation(),
withError: "Missing function declaration component")))
// If there are no fields, it will be a "}"
if currentNode!.nodeType == "}" {
let struc = Declaration(
TypedIdentifier(
id: struct_identifier,
withType: P4QualifiedType(
P4Struct(withName: struct_identifier, andFields: P4StructFields([])))))
return Result.Ok(
(
struc,
context.extern_context
? context
: context.update(
newTypes: context.types.declare(
identifier: struct_identifier, withValue: struc.identifier.type.baseType()))
))
}
var parse_errs: (any Errorable)? = .none
var current_context = context
var parsed_fields: [P4StructFieldIdentifier] = Array()
if currentNode!.nodeType == "struct_declaration_fields" {
currentNode!.enumerateNamedChildren { declaration_field in
switch VariableDeclarationStatement.Compile(
node: declaration_field, withContext: current_context)
{
case .Ok((let declaration, let updated_context)):
let variable_declaration = declaration as! VariableDeclarationStatement
parsed_fields.append(
P4StructFieldIdentifier(
id: variable_declaration.identifier, withType: variable_declaration.initializer.type()
))
current_context = updated_context
case .Error(let e):
parse_errs =
if let e = parse_errs {
parse_errs?.append(error: e)
} else {
e
}
}
}
}
if let parse_errs = parse_errs {
return .Error(ErrorWithLabel("Error(s) parsing select cases", parse_errs))
}
let declared_struct = Declaration(
TypedIdentifier(
id: struct_identifier,
withType: P4QualifiedType(
P4Struct(
withName: struct_identifier, andFields: P4StructFields(parsed_fields)))))
return .Ok(
(
declared_struct,
current_context.extern_context
? current_context
: current_context.update(
newTypes: current_context.types.declare(
identifier: struct_identifier, withValue: declared_struct.identifier.type.baseType()))
))
}
}
extension P4Lang.Parser: CompilableDeclaration {
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(Declaration, CompilerContext)?> {
let parser_node = node
#SkipUnlessNodeType<Node>(node: parser_node, type: "parserDeclaration")
var current_context = context
var walker = Walker(node: parser_node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: parser_node.toSourceLocation(),
withError: "Missing elements of parser declaration")))
if current_node!.nodeType != "parserType" {
return .Error(
ErrorWithLocation(
sourceLocation: current_node!.toSourceLocation(),
withError: "Missing type for parser declaration"))
}
let type_node = current_node
var parser_name: Common.Identifier? = .none
/// TODO: Handle parser parameter lists.
var parameter_list = ParameterList()
do {
var type_node_walker = Walker(node: type_node!)
var type_node_child: Node? = .none
#MustOr(
result: type_node_child, thing: type_node_walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: parser_node.toSourceLocation(),
withError: "Missing elements of parser type in parser declaration")))
if type_node_child!.nodeType == "annotations" {
return .Error(
ErrorWithLocation(
sourceLocation: type_node_child!.toSourceLocation(),
withError: "Annotations in parser type are not yet handled."))
// Will increment indexes here.
}
type_node_walker.next()
#MustOr(
result: type_node_child, thing: type_node_walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: type_node_child!.toSourceLocation(),
withError: "Missing name in parser type declaration")))
switch Identifier.Compile(node: type_node_child!, withContext: current_context) {
case .Ok(let id): parser_name = id
case .Error(let e):
return .Error(e)
}
type_node_walker.next()
#MustOr(
result: type_node_child, thing: type_node_walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: type_node_child!.toSourceLocation(),
withError: "Missing parser parameters")))
switch ParameterList.Compile(node: type_node_child!, withContext: current_context) {
case .Ok(let (parsed_parameter_list, updated_context)):
parameter_list = parsed_parameter_list
current_context = updated_context
case .Error(let e):
return .Error(e)
}
}
// Now, let's put the parameters into scope.
for parameter in parameter_list.parameters {
current_context = current_context.update(
newInstances: current_context.instances.declare(
identifier: parameter.name, withValue: parameter.type))
}
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: parser_node.toSourceLocation(),
withError: "Missing parser declaration component")))
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: parser_node.toSourceLocation(),
withError: "Missing elements of parser declaration")))
if current_node!.nodeType == "parserLocalElements" {
return .Error(
ErrorWithLocation(
sourceLocation: current_node!.toSourceLocation(),
withError: "Parser Local Elements are not yet handled."))
// Will increment indexes here.
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: parser_node.toSourceLocation(),
withError: "Missing body of parser declaration")))
if current_node!.nodeType != "parserStates" {
return .Error(Error(withMessage: "Missing parser states in parser declaration"))
}
switch Parser.Compile(
withName: parser_name!, withParameters: parameter_list, node: current_node!,
withContext: current_context)
{
case Result.Ok((let parser, let updated_context)):
let parser_declaration = Declaration(
TypedIdentifier(id: parser.name, withType: P4QualifiedType(parser)))
// Create a new context with the name of the parser that was just compiled in scope.
return .Ok(
(
parser_declaration,
context.extern_context
? context
: context.update(
newInstances: updated_context.instances.declare(
identifier: parser.name, withValue: parser_declaration.identifier.type))
))
case Result.Error(let error): return .Error(error)
}
}
}
extension Control: CompilableDeclaration {
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(Declaration, CompilerContext)?> {
#SkipUnlessNodeType<Node>(node: node, type: "control_declaration")
var walker = Walker(node: node)
var current_node: Node? = .none
var local_context = context
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing control declaration component")))
guard
case .Ok(let control_name) = Identifier.Compile(
node: current_node!, withContext: local_context)
else {
return Result.Error(
Error(withMessage: "Could not parse a parameter name from \(current_node!.text!)"))
}
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing control declaration component")))
let maybe_control_parameters = ParameterList.Compile(
node: current_node!, withContext: local_context)
guard case .Ok((let control_parameters, let updated_context)) = maybe_control_parameters
else {
return .Error(maybe_control_parameters.error()!)
}
local_context = updated_context
// Before continuing, make sure to put the parameters into context.
var control_scope = local_context.instances.enter()
for parameter in control_parameters.parameters {
control_scope = control_scope.declare(
identifier: parameter.name, withValue: parameter.type)
}
local_context = local_context.update(newInstances: control_scope)
walker.next()
// Skip the '{'
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(Declaration, CompilerContext)?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing control declaration component")))
var actions: [Action] = Array()
var tables: [Table] = Array()
var apply: ApplyStatement? = .none
// Because the final child
// is the '}'.
// \/\/
let body_parse_results = walker.overUntil(n: node.childCount - 1) { current_node in
if current_node.nodeType == "action_declaration" {
let maybe_action_declaration = Action.Compile(
node: current_node, withContext: local_context)
guard
case .Ok((let action_declaration, let updated_context)) = maybe_action_declaration
else {
return .Error(maybe_action_declaration.error()!)
}
actions.append(action_declaration)
local_context = updated_context
// Now, add the declaration into the context.
local_context = local_context.update(
newTypes: local_context.types.declare(
identifier: action_declaration.name, withValue: action_declaration))
} else if current_node.nodeType == "table_declaration" {
let maybe_table_declaration = Table.Compile(
node: current_node, withContext: local_context)
guard
case .Ok((let table_declaration, let updated_context)) = maybe_table_declaration
else {
return .Error(maybe_table_declaration.error()!)
}
tables.append(table_declaration)
local_context = updated_context
} else if current_node.nodeType == "apply_statement" {
// When we see an apply, that is it for the actions and the tables.
let maybe_apply_statement = ApplyStatement.Compile(
node: current_node, withContext: local_context)
guard
case .Ok((let apply_statement, let updated_context)) = maybe_apply_statement
else {
return .Error(maybe_apply_statement.error()!)
}
local_context = updated_context
apply = (apply_statement as! ApplyStatement)
// The apply is the last thing in a control declaration.
// But, that is handled by the compiler.
} else {
return .Error(
ErrorWithLocation(
sourceLocation: current_node.toSourceLocation(),
withError: "Uknown node type in control declaration"))
}
return .Ok(())
}
if case .Error(let e) = body_parse_results {
return .Error(e)
}
// There should only be a single table!
/// TODO: Check the semantics here.
if tables.count > 1 {
/// TODO: Make this error message better.
/// IDEA: Add a "compilation context" for the error message into the `CompilationContext`
// that can be retrieved to make the error messages nicer.
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "More than one table in control declaration"))
}
// Check to make sure that there is an apply.
guard let apply = apply else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing apply in control declaration"
))
}
let declared_control =
Declaration(
TypedIdentifier(
id: control_name,
withType: P4QualifiedType(
Control(
named: control_name, withParameters: control_parameters, withTable: tables[0],
withActions: Actions(withActions: actions), withApply: apply))))
// Don't forget to add the newly declared Control to the instance that we were given
// (and not the one that we entered to do the parsing of this Control).
return .Ok(
(
declared_control,
context.extern_context
? context
: context.update(
newInstances: context.instances.declare(
identifier: control_name, withValue: declared_control.identifier.type))
))
}
}
extension Action: Compilable {
public typealias T = Action
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(P4Lang.Action, CompilerContext)> {
#RequireNodeType<Node, (P4Type, CompilerContext)>(
node: node, type: "action_declaration", nice_type_name: "Action Declaration")
var walker = Walker(node: node)
var current_node: Node? = .none
var current_context = context
// Skip action keyword
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(P4Lang.Action, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing action declaration component"
))
)
guard
case .Ok(let action_name) = Identifier.Compile(
node: current_node!, withContext: current_context)
else {
return Result.Error(
Error(withMessage: "Could not parse an action name from \(current_node!.text!)"))
}
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(P4Lang.Action, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing action declaration component"
))
)
let maybe_action_parameters = ParameterList.Compile(
node: current_node!, withContext: current_context)
guard case .Ok((let action_parameters, let updated_context)) = maybe_action_parameters
else {
return .Error(maybe_action_parameters.error()!)
}
current_context = updated_context
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(P4Lang.Action, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing action declaration component"
))
)
// Add the parameters into scope.
var function_scope = context.instances.enter()
for parameter in action_parameters.parameters {
function_scope = function_scope.declare(
identifier: parameter.name, withValue: parameter.type)
}
let maybe_action_body = BlockStatement.Compile(
node: current_node!, withContext: context.update(newInstances: function_scope))
guard case .Ok((let action_body, _)) = maybe_action_body else {
return .Error(maybe_action_body.error()!)
}
/// TODO: Actions cannot contain switches!
return .Ok(
(
Action(
named: action_name, withParameters: action_parameters,
withBody: (action_body as! BlockStatement)),
current_context
))
}
}
extension TableKeyEntry: Compilable {
public typealias T = TableKeyEntry
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(P4Lang.TableKeyEntry, CompilerContext)> {
#RequireNodeType<Node, (P4Type, CompilerContext)>(
node: node, type: "table_key_entry", nice_type_name: "Table Key Entry")
var walker = Walker(node: node)
var current_node: Node? = .none
let current_context = context
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(P4Lang.TableKeyEntry, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing table key entry declaration component")))
let maybe_keyset_expression = KeysetExpression.compile(
node: current_node!, withContext: current_context)
guard case .Ok(let keyset_expression) = maybe_keyset_expression else {
return Result.Error(maybe_keyset_expression.error()!)
}
walker.next()
// Skip the ':'
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(P4Lang.TableKeyEntry, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing table key entry declaration component")))
let maybe_match_type = TableKeyMatchType.Compile(
node: current_node!, withContext: current_context)
guard case .Ok((let match_type, _)) = maybe_match_type else {
return .Error(maybe_match_type.error()!)
}
return .Ok((TableKeyEntry(keyset_expression as! KeysetExpression, match_type), current_context))
}
}
extension TableKeyMatchType: Compilable {
public typealias T = TableKeyMatchType
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(P4Lang.TableKeyMatchType, CompilerContext)> {
#RequireNodeType<Node, (TableKeyMatchType, CompilerContext)>(
node: node, type: "table_key_match_type", nice_type_name: "Table Key Match Type")
if node.text! == "exact" {
return .Ok((TableKeyMatchType.Exact, context))
}
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "\(node.text!) is not a valid match type)"))
}
}
extension TableKeys: Compilable {
public typealias T = TableKeys
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(P4Lang.TableKeys, CompilerContext)> {
#RequireNodeType<Node, (TableKeyMatchType, CompilerContext)>(
node: node, type: "table_keys", nice_type_name: "Table Keys")
var walker = Walker(node: node)
// Skip the
// keys = {
// 1 2 3
walker.next() // 1
walker.next() // 2
walker.next() // 3
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(P4Lang.TableKeys, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing table keys declaration component in control declaration"))
)
let (keys, errors) = walker.try_map(n: node.childCount - 1, onlyNamed: true) { current_node in
return switch TableKeyEntry.Compile(node: current_node, withContext: context) {
case .Ok((let keyset_expression, _)): .Ok(keyset_expression)
case .Error(let e): .Error(e)
}
}
if !errors.isEmpty {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Error(s) parsing table key: "
+ (errors.map { error in
return "\(error.msg())"
}.joined(separator: ";"))))
}
return .Ok((TableKeys(withEntries: keys), context))
}
}
extension TableActionsProperty: Compilable {
public typealias T = TableActionsProperty
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(TableActionsProperty, CompilerContext)> {
#RequireNodeType<Node, (TableActionsProperty, CompilerContext)>(
node: node, type: "table_actions", nice_type_name: "Table Actions")
var walker = Walker(node: node)
// Skip the
// actions = {
// 1 2 3
walker.next() // 1
walker.next() // 2
walker.next() // 3
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(TableActionsProperty, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing table actions declaration component in control declaration"))
)
let (actions, errors) = walker.try_map(n: node.childCount - 1, onlyNamed: true) {
current_node in
switch Identifier.Compile(node: current_node, withContext: context) {
case .Ok(let listed_action):
switch context.types.lookup(identifier: listed_action) {
case .Ok(let maybe_action):
if maybe_action.eq(rhs: Action()) {
return .Ok(TypedIdentifier(id: listed_action, withType: P4QualifiedType(maybe_action)))
}
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "\(listed_action) does not name an action"))
case .Error(let e): return .Error(e)
}
case .Error(let e): return .Error(e)
}
}
if !errors.isEmpty {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Error(s) parsing table actions: "
+ (errors.map { error in
return "\(error.msg())"
}.joined(separator: ";"))))
}
return .Ok((TableActionsProperty(actions), context))
}
}
extension TablePropertyList: Compilable {
public typealias T = TablePropertyList
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(P4Lang.TablePropertyList, CompilerContext)> {
#RequireNodeType<Node, (P4Type, CompilerContext)>(
node: node, type: "table_property_list", nice_type_name: "Table Property List")
var current_context = context
var keys: [TableKeys] = Array()
var actions: [TableActionsProperty] = Array()
var errors: [any Errorable] = Array()
node.enumerateNamedChildren { child in
if child.nodeType == "table_keys" {
switch TableKeys.Compile(node: child, withContext: current_context) {
case .Ok((let table_key, let updated_context)):
current_context = updated_context
keys.append(table_key)
case .Error(let e): errors.append(e)
}
} else if child.nodeType == "table_actions" {
switch TableActionsProperty.Compile(node: child, withContext: current_context) {
case .Ok((let table_action_property, let updated_context)):
current_context = updated_context
actions.append(table_action_property)
case .Error(let e): errors.append(e)
}
} else {
errors.append(
ErrorWithLocation(
sourceLocation: child.toSourceLocation(),
withError: "Uknown node type in control declaration"))
}
}
if !errors.isEmpty {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Error(s) parsing property list: "
+ (errors.map { error in
return "\(error.msg())"
}.joined(separator: ";"))))
}
// There should be only one table keys!
if keys.count > 1 {
// Todo: Make this error message better.
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "More than one key set in table property list"))
}
// There should be only one table actions!
if actions.count > 1 {
// Todo: Make this error message better.
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "More than one actions in table property list"))
}
if actions.isEmpty {
actions.append(TableActionsProperty())
}
return .Ok((TablePropertyList(withActions: actions[0], withKeys: keys[0]), current_context))
}
}
extension Table: Compilable {
public typealias T = Table
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(P4Lang.Table, CompilerContext)> {
let table_declaration_node = node
#RequireNodeType<Node, (P4Type, CompilerContext)>(
node: table_declaration_node, type: "table_declaration", nice_type_name: "Table Declaration")
var walker = Walker(node: table_declaration_node)
var current_node: Node? = .none
let current_context = context
walker.next() // Skip the XXX?
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(P4Lang.Table, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing table declaration component")
))
guard
case .Ok(let table_name) = Identifier.Compile(
node: current_node!, withContext: current_context)
else {
return Result.Error(
Error(withMessage: "Could not parse a table name from \(current_node!.text!)"))
}
walker.next()
// Skip the '{'
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(P4Lang.Table, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing table declaration component")
))
let maybe_table_property_list = TablePropertyList.Compile(
node: current_node!, withContext: current_context)
guard case .Ok((let table_property_list, _)) = maybe_table_property_list else {
return Result.Error(maybe_table_property_list.error()!)
}
return .Ok(
(Table(withName: table_name, withPropertyList: table_property_list), current_context))
}
}
extension ExternDeclaration: CompilableDeclaration {
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(Declaration, CompilerContext)?> {
let extern_declaration_node = node
#RequireNodeType<Node, (Declaration, CompilerContext)>(
node: extern_declaration_node, type: "extern_declaration",
nice_type_name: "Extern Declaration")
let declaration_node = extern_declaration_node.child(at: 1)!
#RequireNodeType<Node, (Declaration, CompilerContext)>(
node: declaration_node, type: "declaration", nice_type_name: "Declaration")
let declarationed_node = declaration_node.child(at: 0)!
let maybe_declared = Declaration.Compile(
node: declarationed_node, withContext: context.update(newExtern: true))
guard case .Ok(let maybe_declared) = maybe_declared else {
return .Error(maybe_declared.error()!)
}
guard case .some((let declared, _)) = maybe_declared else {
return .Ok(.none)
}
// Before we are okay with this declaration, it must already be registered as an extern
// with the matching "stuff".
let found_ffi = context.ffis.first { ffi in
ffi.type().baseType().eq(rhs: declared.identifier.type.baseType())
}
guard let found_ffi = found_ffi else {
return .Error(
ErrorWithLocation(
sourceLocation: declarationed_node.toSourceLocation(),
withError:
"Could not find a foreign function that matches the extern declaration (\(declared))"))
}
let extern_declaration = Declaration(extern: declared, ffi: found_ffi)
return .Ok(
(
extern_declaration,
context.update(
newExterns: context.externs.declare(
identifier: declared.identifier, withValue: extern_declaration))
))
}
}
-789
View File
@@ -1,789 +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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterP4
protocol CompilableExpression {
static func compile(
node: Node, withContext context: CompilerContext
) -> Result<EvaluatableExpression?>
}
protocol CompilableLValueExpression {
static func compile_as_lvalue(
node: Node, withContext context: CompilerContext
) -> Result<EvaluatableLValueExpression?>
}
extension TypedIdentifier: CompilableExpression {
static func compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Result<EvaluatableExpression?> {
let node = node.child(at: 0)!
#SkipUnlessNodeType<SwiftTreeSitter.Node>(
node: node, type: "identifier")
guard
case Result.Ok(let type) = context.instances.lookup(
identifier: Common.Identifier(name: node.text!))
else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Cannot find \(node.text!) in scope"))
}
return .Ok(TypedIdentifier(name: node.text!, withType: type))
}
}
extension TypedIdentifier: CompilableLValueExpression {
static func compile_as_lvalue(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Result<EvaluatableLValueExpression?> {
let expression = node.child(at: 0)!
#SkipUnlessNodeType<SwiftTreeSitter.Node>(
node: expression, type: "identifier")
let maybe_parsed_expression = TypedIdentifier.compile(node: node, withContext: context)
guard case .Ok(let maybe_typed_identifier) = maybe_parsed_expression else {
return .Error(maybe_parsed_expression.error()!)
}
let typed_identifier_expression = maybe_typed_identifier as! TypedIdentifier
return Result.Ok(typed_identifier_expression)
}
}
extension P4BooleanValue: CompilableExpression {
static func compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Result<EvaluatableExpression?> {
let node = node.child(at: 0)!
#SkipUnlessNodeType<SwiftTreeSitter.Node>(
node: node, type: "booleanLiteralExpression")
if node.text == "false" {
return .Ok(P4Value(P4BooleanValue(withValue: false)))
} else if node.text == "true" {
return .Ok(P4Value(P4BooleanValue(withValue: true)))
}
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Failed to parse boolean literal: \(node.text!)"))
}
}
extension P4IntValue: CompilableExpression {
static func compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Result<EvaluatableExpression?> {
let node = node.child(at: 0)!
#SkipUnlessNodeType<SwiftTreeSitter.Node>(node: node, type: "integer")
if let parsed_int = Int(node.text!) {
return .Ok(P4Value(P4IntValue(withValue: parsed_int)))
} else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Failed to parse integer: \(node.text!)"))
}
}
}
extension P4StringValue: CompilableExpression {
static func compile(
node: SwiftTreeSitter.Node, withContext scopes: CompilerContext
) -> Result<EvaluatableExpression?> {
let node = node.child(at: 0)!
#SkipUnlessNodeType<SwiftTreeSitter.Node>(
node: node, type: "string_literal")
return .Ok(P4Value(P4StringValue(withValue: node.text!)))
}
}
extension KeysetExpression: CompilableExpression {
static func compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(any Common.EvaluatableExpression)?> {
let keyset_expression_node = node.child(at: 0)!
#RequireNodesType<Node, EvaluatableExpression>(
nodes: keyset_expression_node, type: ["expression", "default_keyset"],
nice_type_names: ["expression", "default keyset"])
// If there is a default keyset, that's easy!
if keyset_expression_node.nodeType == "default_keyset" {
return .Ok(KeysetExpression(P4Value(P4SetDefaultValue(withType: context.expected_type!))))
}
// Compile the expression:
let maybe_compiled_set_expression = Expression.Compile(
node: keyset_expression_node, withContext: context)
guard case .Ok(let compiled_expression) = maybe_compiled_set_expression else {
return .Error(maybe_compiled_set_expression.error()!)
}
return .Ok(KeysetExpression(compiled_expression))
}
}
struct Expression {
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<EvaluatableExpression> {
#RequireNodeType<Node, EvaluatableExpression>(
node: node, type: "expression", nice_type_name: "expression")
let expression_node = node.child(at: 0)!
#RequireNodesType<Node, EvaluatableExpression>(
nodes: expression_node, type: ["grouped_expression", "simple_expression"],
nice_type_names: ["grouped expression", "simple expression"])
// If this is a grouped expression, recurse!
if expression_node.nodeType == "grouped_expression" {
return Expression.Compile(node: expression_node.child(at: 1)!, withContext: context)
}
let expression_parsers: [CompilableExpression.Type] = [
P4BooleanValue.self, P4StringValue.self, P4IntValue.self, TypedIdentifier.self,
BinaryOperatorExpression.self, ArrayAccessExpression.self, FieldAccessExpression.self,
FunctionCall.self,
]
for candidate_expression_parser in expression_parsers {
switch candidate_expression_parser.compile(
node: expression_node, withContext: context)
{
case .Ok(.some(let parsed)): return .Ok(parsed)
case .Error(let e): return .Error(e)
default: continue
}
}
return Result.Error(Error(withMessage: "\(node.range): Could not parse into expression"))
}
}
struct LValue {
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<EvaluatableLValueExpression> {
#RequireNodeType<Node, EvaluatableExpression>(
node: node, type: "expression", nice_type_name: "expression")
let expression_node = node.child(at: 0)!
#RequireNodesType<Node, EvaluatableExpression>(
nodes: expression_node, type: ["grouped_expression", "simple_expression"],
nice_type_names: ["grouped expression", "simple expression"])
// If this is a grouped expression, recurse!
if expression_node.nodeType == "grouped_expression" {
return LValue.Compile(node: expression_node.child(at: 1)!, withContext: context)
}
let lvalue_parsers: [CompilableLValueExpression.Type] = [
TypedIdentifier.self, FieldAccessExpression.self, ArrayAccessExpression.self,
]
for candidate_lvalue_parser in lvalue_parsers {
switch candidate_lvalue_parser.compile_as_lvalue(
node: expression_node, withContext: context)
{
case .Ok(.some(let parsed)): return .Ok(parsed)
case .Error(let e): return .Error(e)
default: continue
}
}
return Result.Error(Error(withMessage: "\(node.range): Could not parse into expression"))
}
}
struct Identifier {
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<Common.Identifier> {
return if let node_text_value = node.text {
.Ok(Common.Identifier(name: node_text_value))
} else {
.Error(Error(withMessage: "Could not parse an identifier from \(node)"))
}
}
}
extension SelectExpression: CompilableExpression {
static func compile(
node: Node, withContext context: CompilerContext
) -> Result<EvaluatableExpression?> {
#RequireNodeType<Node, (SelectExpression, CompilerContext)>(
node: node, type: "selectExpression", nice_type_name: "parser select expression")
guard let selector_node = node.child(at: 2),
selector_node.nodeType == "expression"
else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Could not find selector expression"))
}
guard let select_body_node = node.child(at: 5),
select_body_node.nodeType == "selectBody"
else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Could not find select expression body"))
}
let maybe_selector = Expression.Compile(node: selector_node, withContext: context)
guard case .Ok(let selector) = maybe_selector else {
return .Error(
ErrorWithLocation(
sourceLocation: selector_node.toSourceLocation(),
withError:
"Could not parse transition select expression selector expression: \(maybe_selector.error()!)"
))
}
var sces: [SelectCaseExpression] = Array()
var sces_errors: (any Errorable)? = .none
select_body_node.enumerateNamedChildren { current_node in
let maybe_parsed_cse = SelectCaseExpression.compile(
node: current_node, withContext: context.update(newExpectation: selector.type()))
switch maybe_parsed_cse {
case .Ok(let parsed_cse): sces.append(parsed_cse as! SelectCaseExpression)
case .Error(let e):
sces_errors =
if let sces_errors = sces_errors {
sces_errors.append(error: Error(withMessage: "\(maybe_parsed_cse.error()!)"))
} else {
e
}
}
}
if let sces_errors = sces_errors {
return .Error(ErrorWithLabel("Error(s) parsing select cases", sces_errors))
}
return .Ok(
SelectExpression(withSelector: selector, withSelectCaseExpressions: sces),
)
}
}
extension SelectCaseExpression: CompilableExpression {
static func compile(
node: Node, withContext context: CompilerContext
) -> Result<EvaluatableExpression?> {
if node.nodeType != "selectCase" {
return Result.Error(Error(withMessage: "Expected select case not found"))
}
guard let keysetexpression_node = node.child(at: 0),
keysetexpression_node.nodeType == "keysetExpression"
else {
return Result.Error(Error(withMessage: "Missing keyset expression in select case"))
}
guard let targetstate_node = node.child(at: 2),
targetstate_node.nodeType == "identifier"
else {
return Result.Error(Error(withMessage: "Missing target state in select case"))
}
let maybe_parsed_keysetexpression = KeysetExpression.compile(
node: keysetexpression_node, withContext: context)
guard case Result.Ok(let maybe_keysetexpression) = maybe_parsed_keysetexpression else {
return Result.Error(maybe_parsed_keysetexpression.error()!)
}
guard let maybe_keysetexpression = maybe_keysetexpression else {
return Result.Error(
ErrorWithLocation(
sourceLocation: keysetexpression_node.toSourceLocation(),
withError: "Missing expected keyset expression"))
}
let keysetexpression = maybe_keysetexpression as! KeysetExpression
if case .Error(let e) = keysetexpression.compatible(type: context.expected_type!) {
return .Error(
ErrorWithLocation(
sourceLocation: keysetexpression_node.toSourceLocation(), withError: e.msg()))
}
let maybe_parsed_targetstate = Identifier.Compile(
node: targetstate_node, withContext: context)
guard case .Ok(let targetstate) = maybe_parsed_targetstate else {
return Result.Error(maybe_parsed_targetstate.error()!)
}
return .Ok(
SelectCaseExpression(
withKey: keysetexpression, withNextState: targetstate)
)
}
}
extension BinaryOperatorExpression: CompilableExpression {
static func compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Result<(EvaluatableExpression)?> {
let expression = node.child(at: 0)!
#SkipUnlessNodeType<Node>(
node: expression, type: "binaryOperatorExpression")
let binary_operator_expression_node = expression.child(at: 0)!
var walker = Walker(node: binary_operator_expression_node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed binary operator expression"
)))
/// TODO: This macro cannot handle new lines in the arrays
// swift-format-ignore
#RequireNodesType<Node, EvaluatableExpression?>(
nodes: binary_operator_expression_node,
type: ["binaryEqualOperatorExpression", "binaryLessThanOperatorExpression", "binaryLessThanEqualOperatorExpression", "binaryGreaterThanOperatorExpression", "binaryGreaterThanEqualOperatorExpression", "binaryAndOperatorExpression", "binaryOrOperatorExpression", "binaryAddOperatorExpression", "binarySubtractOperatorExpression", "binaryMultiplyOperatorExpression", "binaryDivideOperatorExpression"],
nice_type_names: [ "binary equal operator", "binary less than operator", "binary less than or equal to operator", "binary greater than operator", "binary greater than or equal to operator", "binary and operator", "binary or operator", "binary add operator", "binary subtract operator", "binary multiply operator", "binary divide operator"])
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing LHS for binary operator expression")))
let left_hand_side_raw = current_node!
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing binary operator for binary operator expression")))
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing RHS for binary operator expression")))
let right_hand_side_raw = current_node!
let maybe_left_hand_side = Expression.Compile(node: left_hand_side_raw, withContext: context)
guard case Result.Ok(let left_hand_side) = maybe_left_hand_side else {
return Result.Error(maybe_left_hand_side.error()!)
}
let maybe_right_hand_side = Expression.Compile(node: right_hand_side_raw, withContext: context)
guard case Result.Ok(let right_hand_side) = maybe_right_hand_side else {
return Result.Error(maybe_right_hand_side.error()!)
}
let evaluators:
[String: (String, P4QualifiedType, BinaryOperatorChecker?, BinaryOperatorEvaluator)] = [
"binaryEqualOperatorExpression": (
"Binary Equal", P4QualifiedType(P4Boolean()), Optional<BinaryOperatorChecker>.none,
binary_equal_operator_evaluator
),
"binaryLessThanOperatorExpression": (
"Binary Less Than", P4QualifiedType(P4Boolean()), Optional<BinaryOperatorChecker>.none,
binary_lt_operator_evaluator
),
"binaryLessThanEqualOperatorExpression": (
"Binary Less Than Or Equal", P4QualifiedType(P4Boolean()),
Optional<BinaryOperatorChecker>.none,
binary_lte_operator_evaluator
),
"binaryGreaterThanOperatorExpression": (
"Binary Greater Than", P4QualifiedType(P4Boolean()), Optional<BinaryOperatorChecker>.none,
binary_gt_operator_evaluator
),
"binaryGreaterThanEqualOperatorExpression": (
"Binary Greater Than Or Equal", P4QualifiedType(P4Boolean()),
Optional<BinaryOperatorChecker>.none,
binary_gte_operator_evaluator
),
"binaryAndOperatorExpression": (
"Binary Or", P4QualifiedType(P4Boolean()), binary_and_or_operator_checker,
binary_and_operator_evaluator
),
"binaryOrOperatorExpression": (
"Binary And", P4QualifiedType(P4Boolean()), binary_and_or_operator_checker,
binary_or_operator_evaluator
),
"binaryAddOperatorExpression": (
"Binary Add", P4QualifiedType(P4Int()), binary_int_math_operator_checker,
binary_add_operator_evaluator
),
"binarySubtractOperatorExpression": (
"Binary Subtract", P4QualifiedType(P4Int()), binary_int_math_operator_checker,
binary_subtract_operator_evaluator
),
"binaryMultiplyOperatorExpression": (
"Binary Multiply", P4QualifiedType(P4Int()), binary_int_math_operator_checker,
binary_multiply_operator_evaluator
),
"binaryDivideOperatorExpression": (
"Binary Divide", P4QualifiedType(P4Int()), binary_int_math_operator_checker,
binary_divide_operator_evaluator
),
]
guard let selected_evaluator = evaluators[binary_operator_expression_node.nodeType!] else {
return Result.Error(
Error(withMessage: "No evaluator for \(binary_operator_expression_node.nodeType!)"))
}
if let checker = selected_evaluator.2,
case .Error(let e) = checker(left_hand_side, right_hand_side)
{
return Result.Error(e)
}
return .Ok(
BinaryOperatorExpression(
withEvaluator: (selected_evaluator.0, selected_evaluator.1, selected_evaluator.3),
withLhs: left_hand_side, withRhs: right_hand_side))
}
}
extension ArrayAccessExpression: CompilableExpression {
static func compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<EvaluatableExpression?> {
let expression = node.child(at: 0)!
#SkipUnlessNodeType<Node>(
node: expression, type: "arrayAccessExpression")
let array_access_expression_node = expression
var walker = Walker(node: array_access_expression_node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed array access expression")))
#RequireNodeType<Node, EvaluatableExpression?>(
node: current_node!, type: "expression",
nice_type_name: "array identifier expression")
let array_access_identifier_node = current_node!
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing [ for array access expression")))
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing indexor expression for array access expression")))
#RequireNodeType<Node, EvaluatableExpression?>(
node: current_node!, type: "expression",
nice_type_name: "array indexor expression")
let array_access_indexor_node = current_node!
let maybe_array_identifier = Expression.Compile(
node: array_access_identifier_node, withContext: context)
guard case Result.Ok(let array_identifier) = maybe_array_identifier else {
return Result.Error(maybe_array_identifier.error()!)
}
let maybe_array_type = array_identifier.type()
guard let array_type = maybe_array_type.baseType() as? P4Array else {
return Result.Error(
ErrorWithLocation(
sourceLocation: array_access_identifier_node.toSourceLocation(),
withError: "\(array_identifier) does not name an array type")
)
}
let maybe_array_indexor = Expression.Compile(
node: array_access_indexor_node, withContext: context)
guard case Result.Ok(let array_indexor) = maybe_array_indexor else {
return Result.Error(maybe_array_indexor.error()!)
}
return .Ok(
ArrayAccessExpression(
withName: array_identifier, withType: array_type, withIndexor: array_indexor))
}
}
extension FieldAccessExpression: CompilableExpression {
static func compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(any Common.EvaluatableExpression)?> {
let expression = node.child(at: 0)!
#SkipUnlessNodeType<Node>(
node: expression, type: "fieldAccessExpression")
let field_access_expression_node = expression
var walker = Walker(node: field_access_expression_node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed field access expression")))
#RequireNodeType<Node, EvaluatableExpression?>(
node: current_node!, type: "expression",
nice_type_name: "struct identifier expression")
let struct_identifier_node = current_node!
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing . for field access expression")))
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing field name for field access expression")))
#RequireNodeType<Node, EvaluatableExpression?>(
node: current_node!, type: "identifier",
nice_type_name: "field name")
let field_name_node = current_node!
// Make sure that the identifier really identifies a struct.
let maybe_struct_identifier = Expression.Compile(
node: struct_identifier_node, withContext: context)
guard case Result.Ok(let struct_identifier) = maybe_struct_identifier else {
return Result.Error(maybe_struct_identifier.error()!)
}
guard let struct_type = struct_identifier.type().baseType() as? P4Struct else {
return .Error(
ErrorWithLocation(
sourceLocation: struct_identifier_node.toSourceLocation(),
withError: "\(struct_identifier_node.text!) does not have struct type"))
}
let maybe_field_name = Identifier.Compile(
node: field_name_node, withContext: context)
guard case Result.Ok(let field_name) = maybe_field_name else {
return Result.Error(maybe_field_name.error()!)
}
// Make sure that the field is valid for the struct type.
let maybe_field_type = struct_type.fields.get_field_type(field_name)
guard let field_type = maybe_field_type else {
return .Error(
ErrorWithLocation(
sourceLocation: field_name_node.toSourceLocation(),
withError: "\(field_name) is not a valid field for struct with type \(struct_type)"))
}
return .Ok(
FieldAccessExpression(
withStruct: struct_identifier,
withField: P4StructFieldIdentifier(id: field_name, withType: field_type)))
}
}
extension FieldAccessExpression: CompilableLValueExpression {
static func compile_as_lvalue(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Result<EvaluatableLValueExpression?> {
let expression = node.child(at: 0)!
#SkipUnlessNodeType<Node>(
node: expression, type: "fieldAccessExpression")
let maybe_parsed_expression = FieldAccessExpression.compile(node: node, withContext: context)
guard case .Ok(let maybe_field_access_expression) = maybe_parsed_expression else {
return .Error(maybe_parsed_expression.error()!)
}
let field_access_expression = maybe_field_access_expression as! FieldAccessExpression
return Result.Ok(field_access_expression)
}
}
extension ArrayAccessExpression: CompilableLValueExpression {
static func compile_as_lvalue(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Result<EvaluatableLValueExpression?> {
let expression = node.child(at: 0)!
#SkipUnlessNodeType<Node>(
node: expression, type: "arrayAccessExpression")
let maybe_parsed_expression = ArrayAccessExpression.compile(node: node, withContext: context)
guard case .Ok(let maybe_array_access_expression) = maybe_parsed_expression else {
return .Error(maybe_parsed_expression.error()!)
}
let array_access_expression = maybe_array_access_expression as! ArrayAccessExpression
return Result.Ok(array_access_expression)
}
}
extension FunctionCall: CompilableExpression {
static func compile(
node: Node, withContext context: CompilerContext
) -> Result<EvaluatableExpression?> {
let expression = node.child(at: 0)!
#SkipUnlessNodeType<Node>(
node: expression, type: "function_call")
var walker = Walker(node: expression)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing function call component")))
let maybe_callee_name = Identifier.Compile(
node: current_node!, withContext: context)
guard case .Ok(let callee_name) = maybe_callee_name else {
return Result.Error(maybe_callee_name.error()!)
}
var maybe_callee: Result<(FunctionDeclaration?, Declaration?)> =
switch context.types.lookup(identifier: callee_name) {
case .Ok(let looked_up):
switch looked_up {
case let callee as FunctionDeclaration:
Result<(FunctionDeclaration?, Declaration?)>.Ok((callee, .none)) // What we found is actually a function declaration
default:
Result<(FunctionDeclaration?, Declaration?)>.Error(
ErrorWithLocation(
sourceLocation: current_node!.toSourceLocation(),
withError: "\(callee_name) is not a function"))
}
case .Error(let e): Result<(FunctionDeclaration?, Declaration?)>.Error(e)
}
maybe_callee =
if case .Error(let e) = maybe_callee {
switch context.externs.lookup(identifier: callee_name) {
case .Ok(let callee as Declaration):
// Now, make sure that it is a function declaration!
switch callee.identifier.type.baseType() {
case is FunctionDeclaration: Result.Ok((.none, callee))
default:
.Error(
ErrorWithLocation(
sourceLocation: current_node!.toSourceLocation(),
withError: "\(callee_name) is not a function"))
}
default: .Error(e)
}
} else {
maybe_callee
}
guard case .Ok(let callee) = maybe_callee else {
return .Error(maybe_callee.error()!)
}
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<EvaluatableExpression?>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing function call component")))
let maybe_argument_list = ArgumentList.Compile(node: current_node!, withContext: context)
guard case .Ok((let arguments, _)) = maybe_argument_list else {
return .Error(maybe_argument_list.error()!)
}
// Now, compare the arguments with the parameters:
let params =
switch callee {
case (.some(let callee), .none): Optional<ParameterList>.some(callee.params)
case (.none, .some(let callee)):
Optional<ParameterList>.some((callee.ffi!.type().baseType() as! FunctionDeclaration).params)
default: Optional<ParameterList>.none
}
guard case .some(let params) = params else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Could not lookup the parameters for the called function (\(callee_name))"))
}
if case .Error(let e) = arguments.compatible(params) {
return .Error(e)
}
// All good!
return switch callee {
case (.some(let callee), .none): .Ok(FunctionCall(callee, withArguments: arguments))
case (.none, .some(let callee)): .Ok(FunctionCall(callee.ffi!, withArguments: arguments))
default:
Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Unexpected error occurred calling function named (\(callee_name))"
))
}
}
}
-321
View File
@@ -1,321 +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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
public struct Parser {
public struct LocalElements {
static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(EvaluatableStatement, CompilerContext)> {
let localElementsParsers: [String: CompilableStatement.Type] = [
"variableDeclaration": VariableDeclarationStatement.self
]
guard let parser = localElementsParsers[node.nodeType ?? ""] else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Unparseable statement type (\(node.nodeType ?? "Unknown Statement Type"))"))
}
switch parser.Compile(node: node, withContext: context) {
case Result.Ok(let (parsed, 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)"))
}
}
}
public struct Statement {
static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(EvaluatableStatement, CompilerContext)> {
if node.nodeType != "parserStatement" && node.nodeType != "statement" {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing expected parser statement")
)
}
let statement = node.child(at: 0)!
let statementParsers: [String: CompilableStatement.Type] = [
"assignmentStatement": ParserAssignmentStatement.self,
"expressionStatement": ExpressionStatement.self,
"variableDeclaration": VariableDeclarationStatement.self,
"conditionalStatement": ConditionalStatement.self, "blockStatement": BlockStatement.self,
"return_statement": ReturnStatement.self,
]
guard let parser = statementParsers[statement.nodeType ?? ""] else {
return Result.Error(
ErrorWithLocation(
sourceLocation: statement.toSourceLocation(),
withError:
"Unparseable statement type (\(statement.nodeType ?? "Unknown Statement Type"))"))
}
switch parser.Compile(node: statement, withContext: context) {
case Result.Ok(let (parsed, updated_context)):
return .Ok((parsed, updated_context))
case Result.Error(let e):
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Failed to parse a statement element: \(e)"))
}
}
}
public struct TransitionStatement {
static func Compile(
node: Node, forState state_identifier: Common.Identifier,
withStatements stmts: [EvaluatableStatement], withContext context: CompilerContext
) -> Result<(InstantiatedParserState, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
node: node, type: "parserTransitionStatement", nice_type_name: "parser transition statement"
)
guard let tse_node = node.child(at: 1),
tse_node.nodeType! == "transitionSelectionExpression"
else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Could not find transition select expression"))
}
guard let next_node = tse_node.child(at: 0) else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Could not find the next token in a transition selection expression"))
}
// If the next node is an identifier, we have the simple form ...
if next_node.nodeType == "identifier" {
let maybe_parsed_next_state = Identifier.Compile(
node: next_node, withContext: context)
if case .Ok(let next_state) = maybe_parsed_next_state {
return .Ok(
(
ParserStateDirectTransition(
name: state_identifier, withStatements: stmts, withNextState: next_state), context
))
} else {
return .Error(
Error(
withMessage:
"Could not parse the next state in a transition statement: \(maybe_parsed_next_state.error()!)"
))
}
}
// We know that the next node is a select expression.
return
switch SelectExpression.compile(node: next_node, withContext: context)
{
case .Ok(let tse):
.Ok(
(
ParserStateSelectTransition(
name: state_identifier, withStatements: stmts,
withTransitioniExpression: tse as! SelectExpression), context
))
case .Error(let e): .Error(e)
}
}
}
public struct Statements {
static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<([EvaluatableStatement], CompilerContext)> {
if node.nodeType != "statements" && node.nodeType != "parserStatements" {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Did not find expected statements"))
}
var errors: (any Errorable)? = .none
var current_context = context
var parsed_s: [EvaluatableStatement] = Array()
node.enumerateNamedChildren { node in
switch Statement.Compile(
node: node, withContext: current_context)
{
case .Ok((let parsed_statement, let updated_context)):
current_context = updated_context
parsed_s.append(parsed_statement)
case .Error(let e):
errors =
if let errors = errors {
errors.append(error: e)
} else {
e
}
}
}
if let errors = errors {
return .Error(errors)
}
return Result.Ok((parsed_s, current_context))
}
}
public struct State {
static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(InstantiatedParserState, CompilerContext)> {
var walker = Walker(node: node)
var current_node: Node? = .none
guard let node_type = node.nodeType,
node_type == "parserState"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find a parser state declaration"))
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(InstantiatedParserState, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing elements in parser state declaration")))
if current_node!.nodeType == "annotations" {
return Result.Error(
ErrorWithLocation(
sourceLocation: current_node!.toSourceLocation(),
withError: "Annotations in parser state are not yet handled."))
// Would increment here.
}
// Skip the keyword state
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(InstantiatedParserState, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing elements in parser state declaration")))
let maybe_state_identifier = Identifier.Compile(
node: current_node!, withContext: context)
guard case Result.Ok(let state_identifier) = maybe_state_identifier else {
return Result.Error(maybe_state_identifier.error()!)
}
walker.next()
// Skip the '{'
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(InstantiatedParserState, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing body of state declaration")
))
var errors: (any Errorable)? = .none
var current_context = context
var parsed_s: [EvaluatableStatement] = Array()
if current_node!.nodeType == "parserStatements" {
switch Statements.Compile(
node: current_node!, withContext: current_context)
{
case .Ok(let (state_statements, updated_context)):
parsed_s = state_statements
current_context = updated_context
case .Error(let error):
errors =
if let errors = errors {
errors.append(error: error)
} else {
error
}
}
walker.next()
}
if let errors = errors {
return .Error(errors)
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(InstantiatedParserState, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing transition statement of state declaration")))
return TransitionStatement.Compile(
node: current_node!, forState: state_identifier, withStatements: parsed_s,
withContext: current_context)
}
}
static func Compile(
withName name: Common.Identifier, withParameters parameters: ParameterList, node: Node,
withContext context: CompilerContext
) -> Result<(P4Lang.Parser, CompilerContext)> {
var parser = P4Lang.Parser(withName: name, withParameters: parameters)
// Build a state from each one listed.
var error: (any Errorable)? = .none
var current_context = context
/// TODO: Assert that there is only one.
node.enumerateNamedChildren { parser_state in
if parser_state.nodeType != "parserState" {
return
}
// Parse a state in a nested scope.
switch Parser.State.Compile(
node: parser_state,
withContext: context.update(newInstances: current_context.instances.enter()))
{
case Result.Ok(let (state, updated_context)):
parser.states = parser.states.append(state: state)
current_context = updated_context
case Result.Error(let e): error = e
}
}
if let error = error {
return .Error(error)
}
return Result.Ok((parser, current_context))
}
}
-143
View File
@@ -1,143 +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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
public struct Program {
public static func Compile(_ source: String) -> Result<P4Lang.Program> {
return Program.Compile(source, withGlobalInstances: .none, withGlobalTypes: .none, withFFIs: [])
}
public static func Compile(
_ source: String, withGlobalInstances globalInstances: VarTypeScopes
) -> Result<P4Lang.Program> {
return Program.Compile(
source, withGlobalInstances: globalInstances, withGlobalTypes: .none, withFFIs: [])
}
public static func Compile(
_ source: String, withGlobalInstances globalInstances: VarTypeScopes?,
withGlobalTypes globalTypes: TypeTypeScopes?, withFFIs ffis: [P4FFI] = Array()
) -> Result<P4Lang.Program> {
let maybe_parser = ConfigureP4Parser()
guard case .Ok(let p) = maybe_parser else {
return .Error(maybe_parser.error()!)
}
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"))
}
var program = P4Lang.Program()
// Set up a context for parsing.
var compilation_context = CompilerContext()
// Add our FFIs
compilation_context = compilation_context.update(newFFIs: ffis)
var errors: (any Errorable)? = .none
// If the caller gave any global instances, add them here.
if let globalInstances = globalInstances {
compilation_context = compilation_context.update(newInstances: globalInstances)
}
// If the caller gave any global types, add them here.
if let globalTypes = globalTypes {
compilation_context = compilation_context.update(newTypes: globalTypes)
}
// Try to parse all top-level declarations.
result?.rootNode?.enumerateNamedChildren { (declaration_node: Node) in
let specific_declaration_node = declaration_node.child(at: 0)!
let declaration_parsers: [CompilableDeclaration.Type] = [
Declaration.self, P4Lang.Parser.self,
]
var found_parser = false
for parser in declaration_parsers {
switch parser.Compile(node: specific_declaration_node, withContext: compilation_context) {
case .Ok(.none): {}()
case .Ok(.some((_, let updated_context))):
found_parser = true
compilation_context = updated_context
break
case .Error(let e):
found_parser = true
errors =
if let errors = errors {
errors.append(error: e)
} else {
e
}
break
}
}
// If none of the declaration parsers chose to parse, that's an error, too!
if !found_parser {
let no_parser_error = ErrorWithLocation(
sourceLocation: specific_declaration_node.toSourceLocation(),
withError: "Could not find parser for declaration node"
)
errors =
if let errors = errors {
errors.append(error: no_parser_error)
} else {
no_parser_error
}
}
}
if let errors = errors {
return .Error(errors)
}
// Any of the instances that are in the top-level scope should go into the program!
program.instances = Array(
compilation_context.instances.map { (_, v) in
v
})
// Any of the types that are in the top-level scope should go into the program!
program.types = Array(
compilation_context.types.map { (_, v) in
v
})
// Any of the extern types that are in the top-level scope should go into the program!
program.externs = Array(
compilation_context.externs.map { (_, v) in
v
})
return Result.Ok(program)
}
}
-56
View File
@@ -1,56 +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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
public protocol CompilableStatement {
static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(EvaluatableStatement, CompilerContext)>
}
public protocol CompilableValue {
static func CompileValue(withValue value: String) -> Result<P4DataValue>
}
public protocol CompilableType {
static func CompileType(
type: SwiftTreeSitter.Node, withContext: CompilerContext
) -> Result<P4Type?>
}
public protocol CompilableDeclaration {
/// Info
///
/// Extensions should update the context with the newly declared item _unless_
/// they are in an extern context (``CompilerContext.extern_context``).
static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(Declaration, CompilerContext)?>
}
public protocol Compilable<T> {
associatedtype T
static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(T, CompilerContext)>
}
-376
View File
@@ -1,376 +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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
extension BlockStatement: CompilableStatement {
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(EvaluatableStatement, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
node: node, type: "blockStatement", nice_type_name: "block statement")
var walker = Walker(node: node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(EvaluatableStatement, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed block statement")))
if current_node!.nodeType != "{" {
return Result.Error(
ErrorWithLocation(
sourceLocation: current_node!.toSourceLocation(),
withError: "Missing { on block statement"))
}
var statements: [EvaluatableStatement] = Array()
var parse_err: (any Errorable)? = .none
var current_context = context
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(EvaluatableStatement, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed block statement")))
if current_node!.nodeType == "statements" {
switch Parser.Statements.Compile(
node: current_node!, withContext: current_context)
{
case .Ok(let (parsed_statements, updated_context)):
current_context = updated_context
statements = parsed_statements
case .Error(let error):
parse_err = error
}
walker.next()
}
if let err = parse_err {
return .Error(err)
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<(EvaluatableStatement, CompilerContext)>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed block statement")))
if current_node!.nodeType != "}" {
return Result.Error(
ErrorWithLocation(
sourceLocation: current_node!.toSourceLocation(),
withError: "Missing } on block statement"))
}
return .Ok((BlockStatement(statements), current_context))
}
}
extension ConditionalStatement: CompilableStatement {
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(EvaluatableStatement, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
node: node, type: "conditionalStatement", nice_type_name: "conditional statement")
let maybe_condition_expression = node.child(at: 2)
guard let condition_expression = maybe_condition_expression,
condition_expression.nodeType == "expression"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find condition for conditional statement"))
}
let maybe_thens = node.child(at: 4)
guard let thens = maybe_thens,
thens.nodeType == "statement"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find then statement block for conditional statement"))
}
guard
case .Ok(let condition) = Expression.Compile(
node: condition_expression, withContext: context)
else {
return Result.Error(
Error(withMessage: "Could not parse a conditional expression in a conditional statement"))
}
guard
case .Ok((let thenns, _)) = Parser.Statement.Compile(
node: thens, withContext: context)
else {
return Result.Error(
Error(
withMessage:
"Could not parse the then block in a conditional statement"))
}
let optional_elss: Result<(any EvaluatableStatement, CompilerContext)>? =
if let elss = node.child(at: 6) {
.some(
Parser.Statement.Compile(
node: elss, withContext: context))
} else {
.none
}
if let parsed_elss = optional_elss {
guard
case .Ok((let elss, _)) = parsed_elss
else {
return Result.Error(
Error(
withMessage:
"Could not parse the else block in a conditional statement"))
}
return .Ok(
(ConditionalStatement(condition: condition, withThen: thenns, andElse: elss), context))
}
return .Ok((ConditionalStatement(condition: condition, withThen: thenns), context))
}
}
extension VariableDeclarationStatement: CompilableStatement {
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(EvaluatableStatement, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
node: node, type: "variableDeclaration", nice_type_name: "variable declaration statement")
let maybe_typeref = node.child(at: 0)
guard let typeref = maybe_typeref,
typeref.nodeType == "typeRef"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find type name for variable declaration statement"))
}
let maybe_variablename = node.child(at: 1)
guard let variablename = maybe_variablename,
variablename.nodeType == "identifier"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find identifier name for variable declaration statement"))
}
let maybe_rvalue = node.childCount > 3 ? node.child(at: 3) : .none
guard
case .Ok(let parsed_variablename) = Identifier.Compile(
node: variablename, withContext: context)
else {
return Result.Error(
Error(withMessage: "Could not parse variable name"))
}
guard case .Ok(let declaration_p4_type) = Types.CompileType(type: typeref, withContext: context)
else {
return Result.Error(
Error(withMessage: "Could not parse a P4 type from \(typeref.text!)"))
}
var initializer: EvaluatableExpression? = .none
// If there is an initializer, it must be an expression.
if let initializer_expression = maybe_rvalue {
guard initializer_expression.nodeType == "expression" else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "initial value for declaration statement is not an expression"))
}
let maybe_parsed_rvalue = Expression.Compile(
node: initializer_expression, withContext: context)
guard
case .Ok(let parsed_rvalue) = maybe_parsed_rvalue
else {
return .Error(maybe_parsed_rvalue.error()!)
}
if parsed_rvalue.type().eq(declaration_p4_type) {
initializer = parsed_rvalue
} else {
return Result.Error(
Error(
withMessage:
"Cannot initialize \(parsed_variablename) (with type \(declaration_p4_type)) from expression with type \(parsed_rvalue.type())"
))
}
}
// If there is no initializer, then it must be defaultable.
if initializer == nil {
initializer = declaration_p4_type.def()
}
guard let initializer = initializer else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "No initializer for declaration"))
}
return Result.Ok(
(
VariableDeclarationStatement(
identifier: parsed_variablename, withInitializer: initializer),
// Context with updated names to include the newly declared name.
context.update(
newInstances: context.instances.declare(
identifier: parsed_variablename, withValue: declaration_p4_type))
)
)
}
}
extension ExpressionStatement: CompilableStatement {
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(EvaluatableStatement, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
node: node, type: "expressionStatement", nice_type_name: "expression statement")
let expression_node = node.child(at: 0)!
return switch Expression.Compile(node: expression_node, withContext: context) {
case .Ok(let expression): .Ok((ExpressionStatement(expression), context))
case .Error(let e): .Error(e)
}
}
}
extension ParserAssignmentStatement: CompilableStatement {
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(EvaluatableStatement, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
node: node, type: "assignmentStatement", nice_type_name: "assignment statement")
guard let lvalue_node = node.child(at: 0),
lvalue_node.nodeType == "expression"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing lvalue in assignment statement"))
}
guard let rvalue_node = node.child(at: 2),
rvalue_node.nodeType == "expression"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing rvalue in assignment statement"))
}
let maybe_parsed_rvalue = Expression.Compile(
node: rvalue_node, withContext: context)
guard case Result.Ok(let rvalue) = maybe_parsed_rvalue else {
return Result.Error(maybe_parsed_rvalue.error()!)
}
let maybe_parsed_lvalue = LValue.Compile(node: lvalue_node, withContext: context)
guard case .Ok(let lvalue_identifier) = maybe_parsed_lvalue else {
return Result.Error(maybe_parsed_lvalue.error()!)
}
let check_result = lvalue_identifier.check(to: rvalue, inScopes: context.instances)
guard case .Ok(_) = check_result else {
return Result.Error(
ErrorWithLocation(
sourceLocation: lvalue_node.toSourceLocation(),
withError: "\(check_result.error()!)"))
}
return Result.Ok(
(
ParserAssignmentStatement(
withLValue: lvalue_identifier,
withValue: rvalue
), context
))
}
}
extension ReturnStatement: CompilableStatement {
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(any Common.EvaluatableStatement, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
node: node, type: "return_statement", nice_type_name: "return statement")
let expression_node = node.child(at: 1)!
return switch Expression.Compile(node: expression_node, withContext: context) {
case .Ok(let result):
if result.type().baseType().eq(rhs: context.expected_type!.baseType()) {
.Ok((ReturnStatement(result), context))
} else {
.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError:
"Type of expression in return statement (\(result.type())) is not compatible with function return type (\(context.expected_type!))"
))
}
case .Error(let e): .Error(e)
}
}
}
extension ApplyStatement: CompilableStatement {
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(any Common.EvaluatableStatement, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
node: node, type: "apply_statement", nice_type_name: "apply statement")
let expression_node = node.child(at: 1)!
return switch BlockStatement.Compile(node: expression_node, withContext: context) {
case .Ok((let statement, let updated_context)):
.Ok((ApplyStatement(statement as! BlockStatement), updated_context))
case .Error(let e): .Error(e)
}
}
}
-84
View File
@@ -1,84 +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 P4Lang
import P4Runtime
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
extension P4Boolean: CompilableType {
public static func CompileType(
type: SwiftTreeSitter.Node, withContext: CompilerContext
) -> Common.Result<(any Common.P4Type)?> {
return type.text == "bool" ? .Ok(P4Boolean()) : .Ok(.none)
}
}
extension P4Int: CompilableType {
public static func CompileType(
type: SwiftTreeSitter.Node, withContext: CompilerContext
) -> Common.Result<(any Common.P4Type)?> {
return type.text == "int" ? .Ok(P4Int()) : .Ok(.none)
}
}
extension P4String: CompilableType {
public static func CompileType(
type: SwiftTreeSitter.Node, withContext: CompilerContext
) -> Common.Result<(any Common.P4Type)?> {
return type.text == "string" ? .Ok(P4String()) : .Ok(.none)
}
}
extension P4Struct: CompilableType {
public static func CompileType(
type: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(any Common.P4Type)?> {
let maybe_parsed_type_id = Identifier.Compile(node: type, withContext: context)
guard case .Ok(let parsed_type_id) = maybe_parsed_type_id else {
return .Error(maybe_parsed_type_id.error()!)
}
if case .Ok(let found_type) = context.types.lookup(identifier: parsed_type_id),
let found_struct_type = found_type as? P4Struct
{
return .Ok(found_struct_type)
}
return .Ok(.none)
}
}
public struct Types {
static func CompileType(
type: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Result<P4QualifiedType> {
let type_parsers: [CompilableType.Type] = [
P4Boolean.self, P4Int.self, P4String.self, P4Struct.self,
]
for type_parser in type_parsers {
switch type_parser.CompileType(type: type, withContext: context) {
case .Ok(.some(let type)): return .Ok(P4QualifiedType(type))
case .Ok(.none): continue
case .Error(let e): return .Error(e)
}
}
return Result.Error(Error(withMessage: "Type name not recognized"))
}
}
View File
-291
View File
@@ -1,291 +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 Action: CustomStringConvertible, P4Type, P4DataValue {
public func type() -> any P4Type {
return self
}
public func eq(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case let arhs as Action: self.name == arhs.name
default: false
}
}
public func eq(rhs: any P4Type) -> Bool {
return switch rhs {
case is Action: true
default: false
}
}
public func lt(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let arhs as Action: return self.name < arhs.name
default: return false
}
}
public func lte(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let arhs as Action: return self.name <= arhs.name
default: return false
}
}
public func gt(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let arhs as Action: return self.name > arhs.name
default: return false
}
}
public func gte(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let arhs as Action: return self.name >= arhs.name
default: return false
}
}
public func def() -> P4DataValue? {
return .none
}
public var description: String {
return "Action: "
+ "\(self.name) with parameters \(self.params) and body \(String(describing: self.body))"
}
public var body: BlockStatement?
public var params: ParameterList
public var name: Identifier
public init(
named name: Identifier = Identifier(name: ""),
withParameters parameters: ParameterList = ParameterList([]),
withBody body: BlockStatement? = .none
) {
self.name = name
self.params = parameters
self.body = body
}
}
public struct Actions: CustomStringConvertible {
public let actions: [Action]
public init(withActions actions: [Action]) {
self.actions = actions
}
public var description: String {
return "Actions: "
+ actions.map { action in
return "\(action)"
}.joined(separator: ";")
}
}
public enum TableKeyMatchType {
case Exact
}
public struct TableKeyEntry: CustomStringConvertible {
public let key: KeysetExpression
public let match_type: TableKeyMatchType
public init(_ key: KeysetExpression, _ match: TableKeyMatchType) {
self.key = key
self.match_type = match
}
public var description: String {
return "Table Key Entry: " + "\(self.key): \(self.match_type)"
}
}
public struct TableKeys: CustomStringConvertible {
public let keys: [TableKeyEntry]
public init(withEntries entries: [TableKeyEntry]) {
self.keys = entries
}
public init() {
self.keys = []
}
public var description: String {
return "Table Keys: "
+ self.keys.map { key in
return "\(key)"
}.joined(separator: ";")
}
}
public struct TableActionsProperty: CustomStringConvertible {
public let actions: [TypedIdentifier]
public init(_ actions: [TypedIdentifier] = []) {
self.actions = actions
}
public var description: String {
return "Table Actions: "
+ self.actions.map { action in
return action.description
}.joined(separator: ";")
}
}
public struct TablePropertyList: CustomStringConvertible {
public let actions: TableActionsProperty
public let keys: TableKeys
public init(withActions actions: TableActionsProperty, withKeys keys: TableKeys) {
self.actions = actions
self.keys = keys
}
public var description: String {
return "Table Property List: \(self.actions) \(self.keys)"
}
}
public struct Table: CustomStringConvertible {
public let properties: TablePropertyList
let name: Identifier
public let entries: [(P4Value, TypedIdentifier)]
public init(
withName name: Identifier, withPropertyList property_list: TablePropertyList,
withEntries entries: [(P4Value, TypedIdentifier)] = []
) {
self.name = name
self.properties = property_list
self.entries = entries
}
public var description: String {
return "Table named: \(self.name) \(self.properties)"
}
/// When the control is evaluated, the value of the x in the table is
/// compared to the entries and the match is assocated with an action
/// that is invoked when the match occurs!
public func update(addEntry entry: (P4Value, TypedIdentifier)) -> Table {
return Table(
withName: self.name, withPropertyList: self.properties, withEntries: self.entries + [entry])
}
}
public struct Control: P4Type, P4DataValue, Equatable, CustomStringConvertible {
public static func == (lhs: Control, rhs: Control) -> Bool {
// Two "bare" controls are always equal.
return true
}
public func eq(rhs: any P4Type) -> Bool {
return switch rhs {
case is Control: true
default: false
}
}
public func type() -> any P4Type {
return self
}
// Any operation between two "bare" parser states is always true.
public func eq(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case is Control: true
default: false
}
}
public func lt(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case is Control: true
default: false
}
}
public func lte(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case is Control: true
default: false
}
}
public func gt(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case is Control: true
default: false
}
}
public func gte(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case is Control: true
default: false
}
}
public var description: String {
return "Control named \(self._name) \(self.parameters) \(self.actions) \(self.table)"
}
public let actions: Actions
public let table: Table
let _parameters: ParameterList
let _name: Identifier
let apply: ApplyStatement
public var parameters: ParameterList {
_parameters
}
public var name: Identifier {
_name
}
public init(
named: Identifier, withParameters parameters: ParameterList, withTable table: Table,
withActions actions: Actions, withApply apply: ApplyStatement
) {
self._name = named
self._parameters = parameters
self.actions = actions
self.table = table
self.apply = apply
}
public func updateTable(addEntry entry: (P4Value, TypedIdentifier)) -> Control {
let table = self.table.update(addEntry: entry)
return Control(
named: self.name, withParameters: self.parameters, withTable: table,
withActions: self.actions, withApply: self.apply)
}
public func def() -> P4DataValue? {
return .none
}
}
-132
View File
@@ -1,132 +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 Declaration: P4Type {
public let identifier: TypedIdentifier
public let extern: Bool
public let ffi: P4FFI?
public init(_ id: TypedIdentifier) {
identifier = id
ffi = .none
self.extern = false
}
public init(extern: Declaration, ffi: P4FFI) {
identifier = extern.identifier
self.ffi = ffi
self.extern = true
}
public func eq(rhs: any Common.P4Type) -> Bool {
return switch rhs {
case let rrhs as Declaration:
self.identifier.type.baseType().eq(rhs: rrhs.identifier.type.baseType())
&& self.extern == rrhs.extern
default: false
}
}
public func def() -> P4DataValue? {
/// TODO: Is a default of the extern'd type the right way to go?
return self.identifier.type.baseType().def()
}
public func type() -> any Common.P4Type {
return self
}
public var description: String {
return "Extern \(self.identifier)"
}
}
public struct ExternDeclaration {}
public struct FunctionDeclaration: P4Type, P4DataValue {
public func type() -> any Common.P4Type {
return self
}
public func eq(rhs: any Common.P4Type) -> Bool {
switch rhs {
case let frhs as FunctionDeclaration:
return frhs.tipe.eq(self.tipe) && frhs.params == self.params
default: return false
}
}
public func eq(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let frhs as FunctionDeclaration: return self.eq(rhs: frhs as P4Type)
default: return false
}
}
public func lt(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let frhs as FunctionDeclaration: return self.name < frhs.name
default: return false
}
}
public func lte(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let frhs as FunctionDeclaration: return self.name <= frhs.name
default: return false
}
}
public func gt(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let frhs as FunctionDeclaration: return self.name > frhs.name
default: return false
}
}
public func gte(rhs: any Common.P4DataValue) -> Bool {
switch rhs {
case let frhs as FunctionDeclaration: return self.name >= frhs.name
default: return false
}
}
public func def() -> P4DataValue? {
return .none
}
public var description: String {
return "Function named \(self.name) that returns \(self.tipe) with parameters \(self.params)"
}
public var body: BlockStatement?
public var params: ParameterList
public var name: Identifier
public var tipe: P4QualifiedType
public init(
named name: Identifier, ofType type: P4QualifiedType, withParameters parameters: ParameterList,
withBody body: BlockStatement?
) {
self.name = name
self.tipe = type
self.params = parameters
self.body = body
}
}
-149
View File
@@ -1,149 +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 KeysetExpression {
public let key: EvaluatableExpression
public init(_ key: EvaluatableExpression) {
self.key = key
}
public func compatible(type: P4QualifiedType) -> Result<()> {
if let key_type = self.key.type().baseType() as? P4Set {
if !key_type.set_type().eq(type) {
return .Error(
Error(
withMessage:
"Key expression of type set of type \(key_type.set_type()) is not compatible with selector type \(type)"
))
}
} else if !self.key.type().eq(type) {
return .Error(
Error(
withMessage:
"Key expression of type \(self.key.type()) is not compatible with selector type \(type)"
))
}
return .Ok(())
}
}
public struct SelectCaseExpression {
public let key: KeysetExpression
public let next_state_identifier: Identifier
public let next_state: ParserState?
public init(withKey key: KeysetExpression, withNextState next_state_id: Identifier) {
self.key = key
self.next_state_identifier = next_state_id
self.next_state = .none
}
public init(
withKey key: KeysetExpression, withNextState next_state_id: Identifier,
withNextState next_state: ParserState?
) {
self.key = key
self.next_state_identifier = next_state_id
self.next_state = next_state
}
}
public struct SelectExpression {
public let selector: EvaluatableExpression
public let case_expressions: [SelectCaseExpression]
public init(
withSelector selector: EvaluatableExpression,
withSelectCaseExpressions sces: [SelectCaseExpression]
) {
self.selector = selector
self.case_expressions = sces
}
public func append_checked_sce(sce: SelectCaseExpression) -> SelectExpression {
var new_cses = self.case_expressions
new_cses.append(sce)
return SelectExpression(
withSelector: self.selector, withSelectCaseExpressions: new_cses)
}
}
public typealias NamedBinaryOperatorEvaluator = (
String, P4QualifiedType, (P4Value, P4Value) -> P4DataValue
)
public typealias BinaryOperatorEvaluator = (P4Value, P4Value) -> P4DataValue
public struct BinaryOperatorExpression {
public let evaluator: NamedBinaryOperatorEvaluator
public let left: EvaluatableExpression
public let right: EvaluatableExpression
public init(
withEvaluator evaluator: NamedBinaryOperatorEvaluator, withLhs lhs: EvaluatableExpression,
withRhs rhs: EvaluatableExpression
) {
self.evaluator = evaluator
self.left = lhs
self.right = rhs
}
}
public struct ArrayAccessExpression {
public let indexor: EvaluatableExpression
public let name: EvaluatableExpression
public let type: P4Array
public init(
withName name: EvaluatableExpression, withType type: P4Array,
withIndexor indexor: EvaluatableExpression
) {
self.name = name
self.type = type
self.indexor = indexor
}
}
public struct FieldAccessExpression {
public let field: P4StructFieldIdentifier
public let strct: EvaluatableExpression
public init(withStruct strct: EvaluatableExpression, withField field: P4StructFieldIdentifier) {
self.strct = strct
self.field = field
}
}
public struct FunctionCall {
public let callee: (FunctionDeclaration?, P4FFI?)
public let arguments: ArgumentList
public let return_type: P4Type
public init(_ callee: FunctionDeclaration, withArguments arguments: ArgumentList) {
self.callee = (callee, .none)
self.arguments = arguments
self.return_type = callee.tipe.baseType()
}
public init(_ callee: P4FFI, withArguments arguments: ArgumentList) {
self.callee = (.none, callee)
self.arguments = arguments
/// ASSUME: That the FFI has been checked and the type is always a function declaration.
self.return_type = (callee.type().baseType() as! FunctionDeclaration).tipe.baseType()
}
}
-364
View File
@@ -1,364 +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 LocalElements {
}
public struct LocalElement {
}
public struct ParserAssignmentStatement {
public let lvalue: EvaluatableLValueExpression
public let value: EvaluatableExpression
public init(
withLValue lvalue: EvaluatableLValueExpression, withValue value: EvaluatableExpression
) {
self.lvalue = lvalue
self.value = value
}
}
/// A P4 Parser State
///
/// Note: A P4 Parser State is both a type and a value.
/// This "bare" parser state represents the state more as a type than a value.
public class ParserState: P4Type, P4DataValue, Equatable, CustomStringConvertible {
public static func == (lhs: ParserState, rhs: ParserState) -> Bool {
// Two "bare" parser states are always equal.
return true
}
public func eq(rhs: any Common.P4Type) -> Bool {
return switch rhs {
case is ParserState: true
default: false
}
}
public func type() -> any Common.P4Type {
return self
}
// Any operation between two "bare" parser states is always true.
public func eq(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case is ParserState: true
default: false
}
}
public func lt(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case is ParserState: true
default: false
}
}
public func lte(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case is ParserState: true
default: false
}
}
public func gt(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case is ParserState: true
default: false
}
}
public func gte(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case is ParserState: true
default: false
}
}
public var description: String {
return "Bare Parser State"
}
/// Construct a ParserState
public init() {}
public func def() -> P4DataValue? {
return .none
}
}
/// Instantiated Parser State
///
/// A parser state is both a type and a value. The Instantiated
/// Parser State is the base class for parser states that act more
/// as a value than a type.
public class InstantiatedParserState: ParserState {
public static func == (lhs: InstantiatedParserState, rhs: InstantiatedParserState) -> Bool {
return lhs.state == rhs.state
}
public override func eq(rhs: any Common.P4Type) -> Bool {
return switch rhs {
case is ParserState: true
default: false
}
}
public override func type() -> any Common.P4Type {
return self
}
public override func eq(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case let other as InstantiatedParserState: self.state == other.state
default: false
}
}
public override func lt(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case let other as InstantiatedParserState: self.state < other.state
default: false
}
}
public override func lte(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case let other as InstantiatedParserState: self.state <= other.state
default: false
}
}
public override func gt(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case let other as InstantiatedParserState: self.state > other.state
default: false
}
}
public override func gte(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case let other as InstantiatedParserState: self.state >= other.state
default: false
}
}
public private(set) var state: Identifier
public private(set) var statements: [EvaluatableStatement]
public override var description: String {
return "Name: \(state)"
}
/// Construct a ParserState
public init(
name: Identifier, withStatements stmts: [EvaluatableStatement],
) {
state = name
statements = stmts
}
/// (private) constructor (no transition)
///
/// accept and reject are the only final states and they are constructed internally.
private init(name: Identifier) {
state = name
statements = Array()
}
public override func def() -> any P4DataValue {
return InstantiatedParserState(name: Identifier(name: ""))
}
}
public class ParserStateDirectTransition: InstantiatedParserState {
private let next_state: Identifier
public init(
name: Identifier, withStatements stmts: [EvaluatableStatement],
withNextState next_state: Identifier
) {
self.next_state = next_state
super.init(name: name, withStatements: stmts)
}
public override var description: String {
return "State (Name: \(super.state) (direct transition))"
}
public func get_next_state() -> Identifier {
return self.next_state
}
}
public class ParserStateNoTransition: InstantiatedParserState {
public override init(name: Identifier, withStatements stmts: [any EvaluatableStatement]) {
super.init(name: name, withStatements: stmts)
}
public override var description: String {
return "State (Name: \(super.state) (no transition))"
}
}
public class ParserStateSelectTransition: InstantiatedParserState {
public let selectExpression: SelectExpression
public override var description: String {
return "State (Name: \(super.state) (select transition))"
}
public init(
name: Identifier, withStatements stmts: [any EvaluatableStatement],
withTransitioniExpression te: SelectExpression
) {
self.selectExpression = te
super.init(name: name, withStatements: stmts)
}
}
nonisolated(unsafe) public let accept = ParserStateNoTransition(
name: Identifier(name: "accept"), withStatements: [])
nonisolated(unsafe) public let reject = ParserStateNoTransition(
name: Identifier(name: "reject"), withStatements: [])
public struct ParserStates {
public var states: [InstantiatedParserState] = Array()
public func count() -> Int {
return states.count
}
public func find(withIdentifier id: Identifier) -> ParserState? {
for state in states {
if state.state == id {
return .some(state)
}
}
return .none
}
public init() {
self.states = Array()
}
private init(withStates states: [InstantiatedParserState]) {
self.states = states
}
public func append(state: InstantiatedParserState) -> ParserStates {
var new_states = self.states
new_states.append(state)
return ParserStates(withStates: new_states)
}
}
/// A P4 Parser
///
/// Note: A Parser is both a type _and_ a value.
public struct Parser: P4Type, P4DataValue {
public func type() -> any Common.P4Type {
return self
}
public func eq(rhs: any Common.P4Type) -> Bool {
return switch rhs {
case let parser_rhs as Parser: self.name == parser_rhs.name
default: false
}
}
public func lt(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case let parser_rhs as Parser: self.name < parser_rhs.name
default: false
}
}
public func lte(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case let parser_rhs as Parser: self.name <= parser_rhs.name
default: false
}
}
public func gt(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case let parser_rhs as Parser: self.name > parser_rhs.name
default: false
}
}
public func gte(rhs: any Common.P4DataValue) -> Bool {
return switch rhs {
case let parser_rhs as Parser: self.name >= parser_rhs.name
default: false
}
}
public var states: ParserStates
public var name: Identifier
public var parameters: ParameterList
public init(withName name: Identifier) {
self.states = ParserStates()
self.parameters = ParameterList()
self.name = name
}
public init(withName name: Identifier, withParameters parameters: ParameterList) {
self.states = ParserStates()
self.parameters = parameters
self.name = name
}
public func findStartState() -> ParserState? {
for state in states.states {
if state.state == Identifier(name: "start") {
return state
}
}
return .none
}
public func eq(rhs: any P4DataValue) -> Bool {
return switch rhs {
case let other as Parser: self.name == other.name
default: false
}
}
public var description: String {
return "Parser \(self.name) with parameters: \(parameters) and states: \(self.states)"
}
public func def() -> P4DataValue? {
return .none
}
}
/// Launder a parser state into an instantiated parser state.
public func AsInstantiatedParserState(_ state: ParserState) -> InstantiatedParserState {
return state as! InstantiatedParserState
}
-127
View File
@@ -1,127 +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 ExpressionStatement {
public let expression: EvaluatableExpression
public init(_ expr: EvaluatableExpression) {
self.expression = expr
}
}
public struct Program {
public var types: [P4Type] = Array()
public var externs: [P4Type] = Array()
public var instances: [P4QualifiedType] = Array()
/// Type of closure for filtering results from ``Program/InstancesWithTypes(_:)``
public typealias TypeFilter = (P4QualifiedType) -> Bool
/// Type of closure for filtering results from ``Program/TypesWithTypes(_:)``
public typealias DataTypeFilter = (P4Type) -> Bool
/// Retrieve global instances in the compiled P4 program.
public func InstancesWithTypes() -> [P4QualifiedType] {
return self.instances
}
/// Retrieve global instances in the compiled P4 program.
///
/// Use the given filter to select which of the global instances
/// from the compiled P4 program to retrieve.
///
/// If the compiled P4 program (from the source in the
/// string `p4_program_with_control_decl`) has two Control
/// instances and you only want to select the one named simple,
/// you could use a filter like
///
/// @Snippet(path: "use-program-instanceswithtypes", slice: "include")
///
public func InstancesWithTypes(_ filter: TypeFilter) -> [P4QualifiedType] {
return self.instances.filter { instance in
filter(instance)
}
}
/// Retrieve global types in the compiled P4 program.
public func TypesWithTypes() -> [P4Type] {
return self.types
}
/// Retrieve global types declared in the compiled P4 program.
///
/// Use the given filter to select which of the global types
/// declared in the compiled P4 program to retrieve.
///
/// If the compiled P4 program (from the source in the
/// string `p4_program_with_struct_decl`) has two structs declared and
/// you only want to select the one named `agg`, you could
/// use a filter like
///
/// @Snippet(path: "use-program-typeswithtypes", slice: "include")
///
public func TypesWithTypes(_ filter: DataTypeFilter) -> [P4Type] {
return self.types.filter { instance in
filter(instance)
}
}
/// Retrieve extern types in the compiled P4 program.
public func Externs() -> [P4Type] {
return self.externs
}
/// Retrieve extern types declared in the compiled P4 program.
///
/// Use the given filter to select which of the extern types
/// declared in the compiled P4 program to retrieve.
///
/// If the compiled P4 program (from the source in the
/// string `p4_program_with_struct_decl`) has two extern structs declared and
/// you only want to select the one named `agg`, you could
/// use a filter like
///
/// @Snippet(path: "use-program-typeswithtypes", slice: "include")
///
public func Externs(_ filter: DataTypeFilter) -> [P4Type] {
return self.externs.filter { instance in
filter(instance)
}
}
/// Find the program's main parser
///
/// Note: For now, the main parser is expected to be named main_parser.
public func starting_parser() -> Result<Parser> {
return self.find_parser(withName: Identifier(name: "main_parser"))
}
public func find_parser(withName name: Identifier) -> Result<Parser> {
for instance in self.instances {
guard let parser = instance.baseType() as? Parser else {
continue
}
if parser.name == name {
return .Ok(parser)
}
}
return .Error(Error(withMessage: "Could not find parser named \(name)"))
}
public init() {}
}
-74
View File
@@ -1,74 +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 VariableDeclarationStatement {
public var initializer: EvaluatableExpression
public var identifier: Identifier
public init(identifier: Identifier, withInitializer initializer: EvaluatableExpression) {
self.identifier = identifier
self.initializer = initializer
}
}
public struct ConditionalStatement {
public var condition: EvaluatableExpression
public var thenn: EvaluatableStatement
public var elss: EvaluatableStatement?
public init(condition: EvaluatableExpression, withThen thenn: EvaluatableStatement) {
self.condition = condition
self.thenn = thenn
self.elss = .none
}
public init(
condition: EvaluatableExpression, withThen thenn: EvaluatableStatement,
andElse elss: EvaluatableStatement
) {
self.condition = condition
self.thenn = thenn
self.elss = elss
}
}
public struct BlockStatement {
public var statements: [EvaluatableStatement]
public init(_ statements: [EvaluatableStatement]) {
self.statements = statements
}
}
public struct ReturnStatement {
public let value: EvaluatableExpression
public init(_ value: EvaluatableExpression) {
self.value = value
}
}
public struct ApplyStatement {
public let body: BlockStatement?
public init() { self.body = .none }
public init(_ body: BlockStatement) {
self.body = body
}
}
+680
View File
@@ -0,0 +1,680 @@
// 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
extension P4Value: CST.Categories.Expression {}
public struct CST {
public struct Categories {
public protocol LanguageElement {}
public protocol Expression: Categories.LanguageElement {}
public protocol Statement: Categories.LanguageElement {}
public protocol State: Categories.LanguageElement {}
public protocol Declaration: Categories.Statement {}
}
struct Expression {}
public struct Statements: Categories.Statement {
public let statements: [Categories.Statement]
public init(_ s: [Categories.Statement]) {
self.statements = s
}
}
public struct VariableDeclarationStatement: Categories.Statement {
public var initializer: Categories.Expression?
public var identifier: CST.Identifier
public var tipe: CST.Tipe
public init(
identifier: Identifier, withType tipe: CST.Tipe,
withInitializer initializer: Categories.Expression?
) {
self.identifier = identifier
self.initializer = initializer
self.tipe = tipe
}
}
public struct ConditionalStatement: Categories.Statement {
public var condition: Categories.Expression
public var thenn: Categories.Statement
public var elss: Categories.Statement?
public init(condition: Categories.Expression, withThen thenn: Categories.Statement) {
self.condition = condition
self.thenn = thenn
self.elss = .none
}
public init(
condition: Categories.Expression, withThen thenn: Categories.Statement,
andElse elss: Categories.Statement
) {
self.condition = condition
self.thenn = thenn
self.elss = elss
}
}
public struct BlockStatement: Categories.Statement {
public var statements: Statements
public init(_ statements: Statements) {
self.statements = statements
}
}
public struct ReturnStatement: Categories.Statement {
public let value: Categories.Expression
public init(_ value: Categories.Expression) {
self.value = value
}
}
public struct ApplyStatement: Categories.Statement {
public let body: CST.BlockStatement?
public init() { self.body = .none }
public init(_ body: CST.BlockStatement) {
self.body = body
}
}
public struct Action: CustomStringConvertible {
public var description: String {
return "Action: "
+ "\(self.name) with parameters \(self.params) and body \(String(describing: self.body))"
}
public var body: CST.BlockStatement?
public var params: CST.ParameterList
public var name: CST.Identifier
public init(
named name: CST.Identifier = CST.Identifier(Common.Identifier(name: "")),
withParameters parameters: ParameterList = ParameterList([]),
withBody body: CST.BlockStatement? = .none
) {
self.name = name
self.params = parameters
self.body = body
}
}
public struct Actions: CustomStringConvertible {
public let actions: [CST.Action]
public init(withActions actions: [CST.Action]) {
self.actions = actions
}
public var description: String {
return "Actions: "
+ actions.map { action in
return "\(action)"
}.joined(separator: ";")
}
}
public enum TableKeyMatchType {
case Exact
}
public struct TableKeyEntry: CustomStringConvertible {
public let key: CST.KeysetExpression
public let match_type: CST.TableKeyMatchType
public init(_ key: CST.KeysetExpression, _ match: CST.TableKeyMatchType) {
self.key = key
self.match_type = match
}
public var description: String {
return "Table Key Entry: " + "\(self.key): \(self.match_type)"
}
}
public struct TableKeys: CustomStringConvertible {
public let keys: [CST.TableKeyEntry]
public init(withEntries entries: [CST.TableKeyEntry]) {
self.keys = entries
}
public init() {
self.keys = []
}
public var description: String {
return "Table Keys: "
+ self.keys.map { key in
return "\(key)"
}.joined(separator: ";")
}
}
public struct TableActionsProperty: CustomStringConvertible {
public let actions: [CST.Identifier]
public init(_ actions: [CST.Identifier] = []) {
self.actions = actions
}
public var description: String {
return "Table Actions: "
+ self.actions.map { action in
return "\(action)"
}.joined(separator: ";")
}
}
public struct TablePropertyList: CustomStringConvertible {
public let actions: CST.TableActionsProperty
public let keys: CST.TableKeys
public init(withActions actions: CST.TableActionsProperty, withKeys keys: CST.TableKeys) {
self.actions = actions
self.keys = keys
}
public var description: String {
return "Table Property List: \(self.actions) \(self.keys)"
}
}
public struct Table: CustomStringConvertible {
public let properties: CST.TablePropertyList
let name: CST.Identifier
public let entries: [(P4Value, CST.Identifier)]
public init(
withName name: CST.Identifier, withPropertyList property_list: CST.TablePropertyList,
withEntries entries: [(P4Value, CST.Identifier)] = []
) {
self.name = name
self.properties = property_list
self.entries = entries
}
public var description: String {
return "Table named: \(self.name) \(self.properties)"
}
/// When the control is evaluated, the value of the x in the table is
/// compared to the entries and the match is assocated with an action
/// that is invoked when the match occurs!
public func update(addEntry entry: (P4Value, CST.Identifier)) -> Table {
return Table(
withName: self.name, withPropertyList: self.properties, withEntries: self.entries + [entry])
}
}
public struct Declaration {}
public struct Control: CustomStringConvertible, Categories.Declaration {
public var description: String {
return "Control named \(self._name) \(self.parameters) \(self.actions) \(self.table)"
}
public let actions: CST.Actions
public let table: CST.Table
let _parameters: CST.ParameterList
let _name: CST.Identifier
let apply: CST.ApplyStatement
public var parameters: CST.ParameterList {
_parameters
}
public var name: CST.Identifier {
_name
}
public init(
named: CST.Identifier, withParameters parameters: CST.ParameterList,
withTable table: CST.Table,
withActions actions: CST.Actions, withApply apply: CST.ApplyStatement
) {
self._name = named
self._parameters = parameters
self.actions = actions
self.table = table
self.apply = apply
}
public func updateTable(addEntry entry: (P4Value, CST.Identifier)) -> Control {
let table = self.table.update(addEntry: entry)
return Control(
named: self.name, withParameters: self.parameters, withTable: table,
withActions: self.actions, withApply: self.apply)
}
public func def() -> P4DataValue? {
return .none
}
}
public struct ExternDeclaration: Categories.Declaration {
public let declaration: CST.Categories.Declaration
public init(_ declaration: CST.Categories.Declaration) {
self.declaration = declaration
}
}
public struct FunctionDeclaration: Categories.Declaration {
public var description: String {
return "Function named \(self.name) that returns \(self.tipe) with parameters \(self.params)"
}
public var body: CST.BlockStatement?
public var params: CST.ParameterList
public var name: CST.Identifier
public var tipe: CST.Tipe
public init(
named name: CST.Identifier, ofType type: CST.Tipe,
withParameters parameters: CST.ParameterList,
withBody body: CST.BlockStatement?
) {
self.name = name
self.tipe = type
self.params = parameters
self.body = body
}
}
public struct StructDeclaration: Categories.Declaration {
public let fields: [CST.VariableDeclarationStatement]
public let identifier: CST.Identifier
public init(_ id: CST.Identifier, _ fields: [CST.VariableDeclarationStatement]) {
self.identifier = id
self.fields = fields
}
}
public struct Instantiation: Categories.Statement {
public let name: CST.Identifier
public var tipe: CST.Identifier
public let arguments: CST.ArgumentList
public init(
named name: CST.Identifier, withType tipe: CST.Identifier,
withArguments arguments: CST.ArgumentList
) {
self.name = name
self.arguments = arguments
self.tipe = tipe
}
}
public struct ExpressionStatement: Categories.Statement {
public let expression: Categories.Expression
public init(_ expr: Categories.Expression) {
self.expression = expr
}
}
public struct Identifier: Categories.Expression {
public let id: Common.Identifier
public init(_ id: Common.Identifier) {
self.id = id
}
}
public struct Literal: Categories.Expression {
public let literal: P4Value
public init(_ literal: P4Value) {
self.literal = literal
}
}
public enum KeysetExpression: Categories.Expression {
case Default
case Value(Categories.Expression)
}
public struct SelectCaseExpression: Categories.Expression {
public let key: CST.KeysetExpression
public let next_state_identifier: CST.Identifier
public init(withKey key: CST.KeysetExpression, withNextState next_state_id: CST.Identifier) {
self.key = key
self.next_state_identifier = next_state_id
}
}
public struct SelectExpression: Categories.Expression {
public let selector: Categories.Expression
public let case_expressions: [CST.SelectCaseExpression]
public init(
withSelector selector: Categories.Expression,
withSelectCaseExpressions sces: [CST.SelectCaseExpression]
) {
self.selector = selector
self.case_expressions = sces
}
public func append_checked_sce(sce: CST.SelectCaseExpression) -> CST.SelectExpression {
var new_cses = self.case_expressions
new_cses.append(sce)
return SelectExpression(
withSelector: self.selector, withSelectCaseExpressions: new_cses)
}
}
public enum BinaryOperatorExpressionType {
case Add
case Subtract
case Multiply
case Divide
case Lt
case Lte
case Gt
case Gte
case Eq
case And
case Or
}
public struct BinaryOperatorExpression: Categories.Expression {
public let left: Categories.Expression
public let right: Categories.Expression
public let type: BinaryOperatorExpressionType
public init(
withType tipe: BinaryOperatorExpressionType,
withLhs lhs: Categories.Expression,
withRhs rhs: Categories.Expression
) {
self.type = tipe
self.left = lhs
self.right = rhs
}
}
public struct ArrayAccessExpression: Categories.Expression {
public let indexor: Categories.Expression
public let name: Categories.Expression
public init(
withName name: Categories.Expression,
withIndexor indexor: Categories.Expression
) {
self.name = name
self.indexor = indexor
}
}
public struct FieldAccessExpression: Categories.Expression {
public let field: Identifier
public let strct: Categories.Expression
public init(withStruct strct: Categories.Expression, withField field: Identifier) {
self.strct = strct
self.field = field
}
}
public struct FunctionCall: Categories.Expression {
public let callee: Identifier
public let arguments: ArgumentList
public init(_ callee: Identifier, withArguments arguments: ArgumentList) {
self.callee = callee
self.arguments = arguments
}
}
public struct LocalElements {}
public struct LocalElement {}
public struct ParserAssignmentStatement: Categories.Statement {
public let lvalue: Categories.Expression
public let value: Categories.Expression
public init(
withLValue lvalue: Categories.Expression, withValue value: Categories.Expression
) {
self.lvalue = lvalue
self.value = value
}
}
/// A P4 Parser State
public class ParserState {
let name: Identifier
public let statements: CST.Statements?
public var description: String {
return "Parser State named \(self.name)"
}
public func getName() -> Identifier {
return self.name
}
public func getStatements() -> CST.Statements? {
return self.statements
}
/// Construct a ParserState
public init(_ name: Identifier, _ statements: CST.Statements? = .none) {
self.name = name
self.statements = statements
}
}
/// TransitionStatement
///
/// Only defined to define Compilable extension.
public struct TransitionStatement {}
public class ParserStateDirectTransition: ParserState, Categories.State {
public let next_state_identifier: Identifier?
public init(
name: Identifier, withNextStateIdentifier next_state_id: Identifier,
withStatements stmts: CST.Statements? = .none
) {
self.next_state_identifier = next_state_id
super.init(name, stmts)
}
}
public class ParserStateNoTransition: ParserState, Categories.State {
/// Construct a ParserState
public init(
name: Identifier, withStatements stmts: CST.Statements? = .none
) {
super.init(name, stmts)
}
}
public class ParserStateSelectTransition: ParserState, Categories.State {
public let te: SelectExpression
public init(
name: Identifier, withTransitionExpression te: SelectExpression,
withStatements stmts: CST.Statements? = .none
) {
self.te = te
super.init(name, stmts)
}
}
public struct ParserStates {
public var states: [Categories.State] = Array()
public func count() -> Int {
return states.count
}
public init(_ states: [Categories.State] = Array()) {
self.states = states
}
public func append(state: Categories.State) -> ParserStates {
var new_states = self.states
new_states.append(state)
return ParserStates(new_states)
}
}
/// A P4 Parser
///
/// Note: A Parser is a type
public struct Parser: Categories.Declaration {
public var states: ParserStates
public var name: Identifier
public var parameters: ParameterList
public init(withName name: Identifier) {
self.states = ParserStates()
self.parameters = ParameterList()
self.name = name
}
public init(withName name: Identifier, withParameters parameters: ParameterList) {
self.states = ParserStates()
self.parameters = parameters
self.name = name
}
public var description: String {
return "Parser \(self.name) with parameters: \(parameters) and states: \(self.states)"
}
}
public struct Types {}
public struct Tipe {
public let tipe: P4QualifiedType
public init(_ tipe: P4QualifiedType) {
self.tipe = tipe
}
}
public struct Parameter: CustomStringConvertible {
public var name: CST.Identifier
public var type: CST.Tipe
public init(
identifier: Identifier, withType type: CST.Tipe
) {
self.name = identifier
self.type = type
}
public var description: String {
return "Parameter: \(self.name) with type \(self.type)"
}
}
public struct ParameterList: CustomStringConvertible {
public var parameters: [CST.Parameter]
public init() {
self.parameters = Array()
}
public init(_ parameters: [CST.Parameter]) {
self.parameters = parameters
}
public func addParameter(_ parameter: CST.Parameter) -> CST.ParameterList {
return CST.ParameterList(self.parameters + [parameter])
}
public var description: String {
let parameters = self.parameters.map { parameter in
parameter.description
}.joined(separator: ";")
return "Parameter list: \(parameters)"
}
}
public struct ArgumentList {
public let arguments: [CST.Argument]
public init(_ arguments: [CST.Argument] = []) {
self.arguments = arguments
}
public func addArgument(_ argument: CST.Argument) -> CST.ArgumentList {
return ArgumentList(self.arguments + [argument])
}
}
public struct Argument {
public let index: Int
public let argument: Categories.Expression
public init(_ argument: Categories.Expression, atIndex index: Int) {
self.argument = argument
self.index = index
}
}
public struct Program {
public var statements: Statements
public init(_ stmts: Statements = Statements([])) {
self.statements = stmts
}
}
public struct Statement {}
}
public struct CSTCompilerContext {
public let lexical_context_name: CST.Identifier?
public let lexical_context_statements: CST.Statements?
public let extern_context: Bool
public init(
_ name: CST.Identifier? = .none, _ stmts: CST.Statements? = .none, _ extern: Bool = false
) {
self.lexical_context_name = name
self.lexical_context_statements = stmts
self.extern_context = extern
}
public func update(withContextName cn: CST.Identifier?) -> CSTCompilerContext {
return CSTCompilerContext(cn, self.lexical_context_statements, self.extern_context)
}
public func update(withContextStatements stmts: CST.Statements?) -> CSTCompilerContext {
return CSTCompilerContext(self.lexical_context_name, stmts, self.extern_context)
}
public func update(withExtern extern: Bool) -> CSTCompilerContext {
return CSTCompilerContext(self.lexical_context_name, self.lexical_context_statements, extern)
}
}
@@ -16,13 +16,16 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import Common
import SwiftTreeSitter
import TreeSitterExtensions
import TreeSitterP4
public struct AttributedP4Type {
public let type: P4Type
public let attributes: P4QualifiedType
public init(_ type: P4Type, _ attributes: P4QualifiedType) {
self.type = type
self.attributes = attributes
extension Node {
public func toSourceLocation() -> SourceLocation {
return SourceLocation(self.range.location, self.range.length)
}
}
@attached(member, names: named(ParseStatement))
public macro deriveParsableStatement() =
#externalMacro(module: "Macros", type: "DeriveParsableStatement")
+57
View File
@@ -0,0 +1,57 @@
// 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 TreeSitterExtensions
import TreeSitterP4
let p4lang = Language(tree_sitter_p4())
public func ConfigureP4Parser() -> Result<SwiftTreeSitter.Parser> {
let p = SwiftTreeSitter.Parser.init()
do {
try p.setLanguage(p4lang)
} catch {
return Result.Error(Error(withMessage: "Could not configure the P4 parser"))
}
return .Ok(p)
}
public func compile(_ s: String) -> Result<CST.Program> {
let maybe_parser = ConfigureP4Parser()
guard case .Ok(let p) = maybe_parser else {
return .Error(maybe_parser.error()!)
}
let result = p.parse(s)
guard let tree = result,
!tree.isError(lang: p4lang),
!tree.containsMissing(lang: p4lang)
else {
return Result.Error(Error(withMessage: "Could not parse the P4 program"))
}
guard let root = tree.rootNode else {
return Result.Error(Error(withMessage: "No P4 program compiled"))
}
let compilation_context = CSTCompilerContext()
return CST.Program.Parse(node: root, withContext: compilation_context)
}
@@ -16,3 +16,18 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import Common
public struct CompilationFeedback {
public let feedback: String
static func markup(
_ source: SourceCode, _ errors: [any Errorable], _ formatter: any Formattable
) -> String {
errors.map { $0.format(formatter, source) }.joined(separator: "\n")
}
public init(_ source: SourceCode, _ errors: [any Errorable], _ formatter: any Formattable) {
self.feedback = Self.markup(source, errors, formatter)
}
}
File diff suppressed because it is too large Load Diff
+577
View File
@@ -0,0 +1,577 @@
// 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
extension CST.Identifier: ParsableExpression {
public static func ParseExpression(
node: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
#RequireNodeType<Node, CST.Categories.Expression>(
node: node, type: "identifier", nice_type_name: "Identifier")
/// TODO: If there is a value here, then we can make this a compile-time constant!
return .Ok(CST.Identifier(Common.Identifier(name: node.text!)))
}
}
extension P4BooleanValue: ParsableExpression {
public static func ParseExpression(
node: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
let node = node.child(at: 0)!
#RequireNodeType<Node, CST.Categories.Expression>(
node: node, type: "booleanLiteralExpression", nice_type_name: "Boolean Literal Expression")
if node.text == "false" {
return .Ok(CST.Literal(P4Value(P4BooleanValue(withValue: false))))
} else if node.text == "true" {
return .Ok(CST.Literal(P4Value(P4BooleanValue(withValue: true))))
}
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Failed to parse boolean literal: \(node.text!)"))
}
}
extension P4IntValue: ParsableExpression {
public static func ParseExpression(
node: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
let node = node.child(at: 0)!
#RequireNodesType<Node, CST.Categories.Expression>(
nodes: node, type: ["integer", "integer_elaborated"],
nice_type_names: ["Integer", "Elaborated Integer"])
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(CST.Literal(P4Value(P4IntValue(withValue: parsed_int, andWidth: bit_width))))
} else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Failed to parse integer: \(node.text!)"))
}
}
}
extension P4StringValue: ParsableExpression {
public static func ParseExpression(
node: SwiftTreeSitter.Node, withContext scopes: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
let node = node.child(at: 0)!
#RequireNodeType<Node, CST.Categories.Expression>(
node: node, type: "string_literal", nice_type_name: "String Literal")
return .Ok(CST.Literal(P4Value(P4StringValue(withValue: node.text!))))
}
}
extension CST.Expression: Parsable {
public static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
#RequireNodeType<Node, CST.Categories.Expression>(
node: node, type: "expression", nice_type_name: "expression")
let expression_node = node.child(at: 0)!
#RequireNodesType<Node, CST.Categories.Expression>(
nodes: expression_node, type: ["grouped_expression", "simple_expression"],
nice_type_names: ["grouped expression", "simple expression"])
// If this is a grouped expression, recurse!
if expression_node.nodeType == "grouped_expression" {
return CST.Expression.Parse(node: expression_node.child(at: 1)!, withContext: context)
}
let expression_parsers: [ParsableExpression.Type] = [
P4BooleanValue.self, P4StringValue.self, P4IntValue.self, CST.Identifier.self,
CST.BinaryOperatorExpression.self, CST.ArrayAccessExpression.self,
CST.FieldAccessExpression.self,
CST.FunctionCall.self,
]
for candidate_expression_parser in expression_parsers {
switch candidate_expression_parser.ParseExpression(
node: expression_node, withContext: context)
{
case .Ok(let parsed): return .Ok(parsed)
case .Error(let e): return .Error(e)
}
}
return Result.Error(Error(withMessage: "\(node.range): Could not parse into expression"))
}
}
extension CST.KeysetExpression: ParsableExpression {
public static func ParseExpression(
node: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
let keyset_expression_node = node.child(at: 0)!
#RequireNodesType<Node, CST.Categories.Expression>(
nodes: keyset_expression_node, type: ["expression", "default_keyset"],
nice_type_names: ["expression", "default keyset"])
// If there is a default keyset, that's easy!
if keyset_expression_node.nodeType == "default_keyset" {
return .Ok(CST.KeysetExpression.Default)
}
// Compile the expression:
let maybe_compiled_set_expression = CST.Expression.Parse(
node: keyset_expression_node, withContext: context)
guard case .Ok(let compiled_expression) = maybe_compiled_set_expression else {
return .Error(maybe_compiled_set_expression.error()!)
}
return .Ok(CST.KeysetExpression.Value(compiled_expression))
}
}
extension CST.SelectExpression: ParsableExpression {
public static func ParseExpression(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
#RequireNodeType<Node, (CST.SelectExpression, CSTCompilerContext)>(
node: node, type: "selectExpression", nice_type_name: "parser select expression")
guard let selector_node = node.child(at: 2),
selector_node.nodeType == "expression"
else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Could not find selector expression"))
}
guard let select_body_node = node.child(at: 5),
select_body_node.nodeType == "selectBody"
else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Could not find select expression body"))
}
let maybe_selector = CST.Expression.Parse(node: selector_node, withContext: context)
guard case .Ok(let selector) = maybe_selector else {
return .Error(
ErrorWithLocation(
sourceLocation: selector_node.toSourceLocation(),
withError:
"Could not parse transition select expression selector expression: \(maybe_selector.error()!)"
))
}
var sces: [CST.SelectCaseExpression] = Array()
var sces_errors: (any Errorable)? = .none
select_body_node.enumerateNamedChildren { current_node in
let maybe_parsed_cse = CST.SelectCaseExpression.ParseExpression(
node: current_node, withContext: context)
switch maybe_parsed_cse {
case .Ok(let parsed_cse): sces.append(parsed_cse as! CST.SelectCaseExpression)
case .Error(let e):
sces_errors =
if let sces_errors = sces_errors {
sces_errors.append(error: Error(withMessage: "\(maybe_parsed_cse.error()!)"))
} else {
e
}
}
}
if let sces_errors = sces_errors {
return .Error(ErrorWithLabel("Error(s) parsing select cases", sces_errors))
}
return .Ok(
CST.SelectExpression(withSelector: selector, withSelectCaseExpressions: sces),
)
}
}
extension CST.SelectCaseExpression: ParsableExpression {
public static func ParseExpression(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
#RequireNodeType<Node, CST.Categories.Expression>(
node: node, type: "selectCase", nice_type_name: "Select Case")
guard let keysetexpression_node = node.child(at: 0),
keysetexpression_node.nodeType == "keysetExpression"
else {
return Result.Error(Error(withMessage: "Missing keyset expression in select case"))
}
guard let targetstate_node = node.child(at: 2),
targetstate_node.nodeType == "identifier"
else {
return Result.Error(Error(withMessage: "Missing target state in select case"))
}
let maybe_parsed_keysetexpression = CST.KeysetExpression.ParseExpression(
node: keysetexpression_node, withContext: context)
guard case Result.Ok(let maybe_keysetexpression) = maybe_parsed_keysetexpression else {
return Result.Error(maybe_parsed_keysetexpression.error()!)
}
let keysetexpression = maybe_keysetexpression as! CST.KeysetExpression
let maybe_parsed_targetstate = CST.Identifier.ParseExpression(
node: targetstate_node, withContext: context)
guard case .Ok(let targetstate) = maybe_parsed_targetstate else {
return Result.Error(maybe_parsed_targetstate.error()!)
}
return .Ok(
CST.SelectCaseExpression(
withKey: keysetexpression, withNextState: targetstate as! CST.Identifier)
)
}
}
extension CST.BinaryOperatorExpression: ParsableExpression {
public static func ParseExpression(
node: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
let expression = node.child(at: 0)!
#RequireNodeType<Node, CST.Categories.Expression>(
node: expression, type: "binaryOperatorExpression",
nice_type_name: "Binary Operator Expression")
let binary_operator_expression_node = expression.child(at: 0)!
var walker = Walker(node: binary_operator_expression_node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed binary operator expression"
)))
/// TODO: This macro cannot handle new lines in the arrays
// swift-format-ignore
#RequireNodesType<Node, CST.Categories.Expression>(
nodes: binary_operator_expression_node,
type: ["binaryEqualOperatorExpression", "binaryLessThanOperatorExpression", "binaryLessThanEqualOperatorExpression", "binaryGreaterThanOperatorExpression", "binaryGreaterThanEqualOperatorExpression", "binaryAndOperatorExpression", "binaryOrOperatorExpression", "binaryAddOperatorExpression", "binarySubtractOperatorExpression", "binaryMultiplyOperatorExpression", "binaryDivideOperatorExpression"],
nice_type_names: [ "binary equal operator", "binary less than operator", "binary less than or equal to operator", "binary greater than operator", "binary greater than or equal to operator", "binary and operator", "binary or operator", "binary add operator", "binary subtract operator", "binary multiply operator", "binary divide operator"])
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing LHS for binary operator expression")))
let left_hand_side_raw = current_node!
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing binary operator for binary operator expression")))
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing RHS for binary operator expression")))
let right_hand_side_raw = current_node!
let maybe_left_hand_side = CST.Expression.Parse(
node: left_hand_side_raw, withContext: context)
guard case Result.Ok(let left_hand_side) = maybe_left_hand_side else {
return Result.Error(maybe_left_hand_side.error()!)
}
let maybe_right_hand_side = CST.Expression.Parse(
node: right_hand_side_raw, withContext: context)
guard case Result.Ok(let right_hand_side) = maybe_right_hand_side else {
return Result.Error(maybe_right_hand_side.error()!)
}
let evaluators: [String: (String, P4QualifiedType, CST.BinaryOperatorExpressionType)] = [
"binaryEqualOperatorExpression": (
"Binary Equal", P4QualifiedType(P4Boolean()),
CST.BinaryOperatorExpressionType.Eq
),
"binaryLessThanOperatorExpression": (
"Binary Less Than", P4QualifiedType(P4Boolean()),
CST.BinaryOperatorExpressionType.Lt
),
"binaryLessThanEqualOperatorExpression": (
"Binary Less Than Or Equal", P4QualifiedType(P4Boolean()),
CST.BinaryOperatorExpressionType.Lte
),
"binaryGreaterThanOperatorExpression": (
"Binary Greater Than", P4QualifiedType(P4Boolean()),
CST.BinaryOperatorExpressionType.Gt
),
"binaryGreaterThanEqualOperatorExpression": (
"Binary Greater Than Or Equal", P4QualifiedType(P4Boolean()),
CST.BinaryOperatorExpressionType.Gte
),
"binaryAndOperatorExpression": (
"Binary Or", P4QualifiedType(P4Boolean()),
CST.BinaryOperatorExpressionType.And
),
"binaryOrOperatorExpression": (
"Binary And", P4QualifiedType(P4Boolean()),
CST.BinaryOperatorExpressionType.Or
),
"binaryAddOperatorExpression": (
"Binary Add", P4QualifiedType(P4Int()),
CST.BinaryOperatorExpressionType.Add
),
"binarySubtractOperatorExpression": (
"Binary Subtract", P4QualifiedType(P4Int()),
CST.BinaryOperatorExpressionType.Subtract
),
"binaryMultiplyOperatorExpression": (
"Binary Multiply", P4QualifiedType(P4Int()),
CST.BinaryOperatorExpressionType.Multiply
),
"binaryDivideOperatorExpression": (
"Binary Divide", P4QualifiedType(P4Int()),
CST.BinaryOperatorExpressionType.Divide
),
]
guard let selected_evaluator = evaluators[binary_operator_expression_node.nodeType!] else {
return Result.Error(
Error(withMessage: "No evaluator for \(binary_operator_expression_node.nodeType!)"))
}
return .Ok(
CST.BinaryOperatorExpression(
withType: selected_evaluator.2,
withLhs: left_hand_side, withRhs: right_hand_side))
}
}
extension CST.ArrayAccessExpression: ParsableExpression {
public static func ParseExpression(
node: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
let expression = node.child(at: 0)!
#RequireNodeType<Node, CST.Categories.Expression>(
node: expression, type: "arrayAccessExpression", nice_type_name: "Array Access Expression")
let array_access_expression_node = expression
var walker = Walker(node: array_access_expression_node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed array access expression")))
#RequireNodeType<Node, CST.Categories.Expression>(
node: current_node!, type: "expression",
nice_type_name: "array identifier expression")
let array_access_identifier_node = current_node!
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing [ for array access expression")))
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing indexor expression for array access expression")))
#RequireNodeType<Node, CST.Categories.Expression>(
node: current_node!, type: "expression",
nice_type_name: "array indexor expression")
let array_access_indexor_node = current_node!
let maybe_array_identifier = CST.Expression.Parse(
node: array_access_identifier_node, withContext: context)
guard case Result.Ok(let array_identifier) = maybe_array_identifier else {
return Result.Error(maybe_array_identifier.error()!)
}
let maybe_array_indexor = CST.Expression.Parse(
node: array_access_indexor_node, withContext: context)
guard case Result.Ok(let array_indexor) = maybe_array_indexor else {
return Result.Error(maybe_array_indexor.error()!)
}
return .Ok(
CST.ArrayAccessExpression(
withName: array_identifier, withIndexor: array_indexor))
}
}
extension CST.FieldAccessExpression: ParsableExpression {
public static func ParseExpression(
node: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
let expression = node.child(at: 0)!
#RequireNodeType<Node, CST.Categories.Expression>(
node: expression, type: "fieldAccessExpression", nice_type_name: "Array Access Expression")
let field_access_expression_node = expression
var walker = Walker(node: field_access_expression_node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed field access expression")))
#RequireNodeType<Node, CST.Categories.Expression>(
node: current_node!, type: "expression",
nice_type_name: "struct identifier expression")
let struct_identifier_node = current_node!
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing . for field access expression")))
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing field name for field access expression")))
#RequireNodeType<Node, CST.Categories.Expression>(
node: current_node!, type: "identifier",
nice_type_name: "field name")
let field_name_node = current_node!
// Make sure that the identifier really identifies a struct.
let maybe_struct_identifier = CST.Expression.Parse(
node: struct_identifier_node, withContext: context)
guard case Result.Ok(let struct_identifier) = maybe_struct_identifier else {
return Result.Error(maybe_struct_identifier.error()!)
}
let maybe_field_name = CST.Identifier.ParseExpression(
node: field_name_node, withContext: context)
guard case Result.Ok(let field_name) = maybe_field_name else {
return Result.Error(maybe_field_name.error()!)
}
return .Ok(
CST.FieldAccessExpression(
withStruct: struct_identifier,
withField: field_name as! CST.Identifier))
}
}
extension CST.FunctionCall: ParsableExpression {
public static func ParseExpression(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression> {
let expression = node.child(at: 0)!
#RequireNodeType<Node, CST.Categories.Expression>(
node: expression, type: "function_call", nice_type_name: "Function Call")
var walker = Walker(node: expression)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing function call component")))
let maybe_callee_name = CST.Identifier.ParseExpression(
node: current_node!, withContext: context)
guard case .Ok(let callee_name) = maybe_callee_name else {
return Result.Error(maybe_callee_name.error()!)
}
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.Expression>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing function call component")))
let maybe_argument_list = CST.ArgumentList.Parse(node: current_node!, withContext: context)
guard case .Ok(let arguments) = maybe_argument_list else {
return .Error(maybe_argument_list.error()!)
}
return .Ok(CST.FunctionCall(callee_name as! CST.Identifier, withArguments: arguments))
}
}
+143
View File
@@ -0,0 +1,143 @@
// 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 TreeSitterExtensions
import TreeSitterP4
extension CST.LocalElements: Parsable {
public typealias C = CST.Categories.Statement
public static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Statement> {
let localElementsParsers: [String: ParsableStatement.Type] = [
"variableDeclaration": CST.VariableDeclarationStatement.self
]
guard let parser = localElementsParsers[node.nodeType ?? ""] else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Unparseable statement type (\(node.nodeType ?? "Unknown Statement Type"))"))
}
switch parser.ParseStatement(node: node, withContext: context) {
case Result.Ok(let parsed):
return Result.Ok(parsed)
case Result.Error(let e):
return Result.Error(Error(withMessage: "Failed to parse local element: \(e)"))
}
}
}
extension CST.ParserState: Parsable {
public typealias C = CST.Categories.State
public static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.State> {
var walker = Walker(node: node)
var current_node: Node? = .none
guard let node_type = node.nodeType,
node_type == "parserState"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find a parser state declaration"))
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.State>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing elements in parser state declaration")))
if current_node!.nodeType == "annotations" {
return Result.Error(
ErrorWithLocation(
sourceLocation: current_node!.toSourceLocation(),
withError: "Annotations in parser state are not yet handled."))
// Would increment here.
}
// Skip the keyword state
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.State>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing elements in parser state declaration")))
let maybe_state_identifier = CST.Identifier.ParseExpression(
node: current_node!, withContext: context)
guard case Result.Ok(let state_identifier) = maybe_state_identifier else {
return Result.Error(maybe_state_identifier.error()!)
}
walker.next()
// Skip the '{'
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.State>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing body of state declaration")
))
var errors: (any Errorable)? = .none
var parsed_s: CST.Statements? = .none
if current_node!.nodeType == "parserStatements" {
switch CST.Statements.Parse(
node: current_node!, withContext: context)
{
case .Ok(let state_statements):
parsed_s = state_statements
case .Error(let error):
errors =
if let errors = errors {
errors.append(error: error)
} else {
error
}
}
walker.next()
}
if let errors = errors {
return .Error(errors)
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Categories.State>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing transition statement of state declaration")))
let updated_context = context.update(withContextName: (state_identifier as! CST.Identifier))
.update(withContextStatements: parsed_s)
return CST.TransitionStatement.Parse(node: current_node!, withContext: updated_context)
}
}
+72
View File
@@ -0,0 +1,72 @@
// 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
extension CST.Program: Parsable {
public typealias C = CST.Program
public static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Common.Result<CST.Program> {
var statements: [CST.Categories.Statement] = Array()
var errors: (any Errorable)? = .none
// Try to parse all top-level declarations.
node.enumerateNamedChildren { (declaration_node: Node) in
let declaration_parsers: [String: ParsableStatement.Type] = [
"declaration": CST.Declaration.self,
"instantiation": CST.Instantiation.self,
]
if let parser = declaration_parsers[declaration_node.nodeType!] {
let r = parser.ParseStatement(node: declaration_node, withContext: context)
switch r {
case .Ok(let compiled):
statements.append(compiled)
case .Error(let e):
errors =
if let errors = errors {
errors.append(error: e)
} else {
e
}
}
} else {
let e = ErrorWithLocation(
sourceLocation: declaration_node.toSourceLocation(),
withError:
"\(declaration_node.nodeType!) cannot be at a P4 program top level")
errors =
if let errors = errors {
errors.append(error: e)
} else {
e
}
}
}
if let errors = errors {
return .Error(errors)
}
return Result.Ok(CST.Program(CST.Statements(statements)))
}
}
+566
View File
@@ -0,0 +1,566 @@
// 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 TreeSitterExtensions
import TreeSitterP4
extension CST.BlockStatement: Parsable {
public typealias C = CST.BlockStatement
public static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.BlockStatement> {
/*
#RequireNodeType<Node, AST.BlockStatement>(
node: node, type: "blockStatement", nice_type_name: "block statement")
var walker = Walker(node: node)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<AST.BlockStatement>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed block statement")))
if current_node!.nodeType != "{" {
return Result.Error(
ErrorWithLocation(
sourceLocation: current_node!.toSourceLocation(),
withError: "Missing { on block statement"))
}
var statements: [AST.AnStatement] = Array()
var parse_err: (any Errorable)? = .none
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<AST.BlockStatement>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed block statement")))
if current_node!.nodeType == "statements" {
switch SpecialCompilers.Statements.Parse(
node: current_node!, withContext: context)
{
case .Ok(let parsed_statements):
statements = parsed_statements
case .Error(let error):
parse_err = error
}
walker.next()
}
if let err = parse_err {
return .Error(err)
}
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<AST.BlockStatement>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Malformed block statement")))
if current_node!.nodeType != "}" {
return Result.Error(
ErrorWithLocation(
sourceLocation: current_node!.toSourceLocation(),
withError: "Missing } on block statement"))
}
return .Ok(AST.BlockStatement(statements))
*/
return .Ok(CST.BlockStatement(CST.Statements([])))
}
}
@deriveParsableStatement
extension CST.BlockStatement: ParsableStatement {}
extension CST.ConditionalStatement: Parsable {
public typealias C = CST.ConditionalStatement
public static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.ConditionalStatement> {
#RequireNodeType<Node, CST.ConditionalStatement>(
node: node, type: "conditionalStatement", nice_type_name: "conditional statement")
let maybe_condition_expression = node.child(at: 2)
guard let condition_expression = maybe_condition_expression,
condition_expression.nodeType == "expression"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find condition for conditional statement"))
}
let maybe_thens = node.child(at: 4)
guard let thens = maybe_thens,
thens.nodeType == "statement"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find then statement block for conditional statement"))
}
guard
case .Ok(let condition) = CST.Expression.Parse(
node: condition_expression, withContext: context)
else {
return Result.Error(
Error(withMessage: "Could not parse a conditional expression in a conditional statement"))
}
guard
case .Ok(let thenns) = CST.Statement.Parse(
node: thens, withContext: context)
else {
return Result.Error(
Error(
withMessage:
"Could not parse the then block in a conditional statement"))
}
let optional_elss: Result<CST.Categories.Statement>? =
if let elss = node.child(at: 6) {
.some(
CST.Statement.Parse(
node: elss, withContext: context))
} else {
.none
}
if let parsed_elss = optional_elss {
guard
case .Ok(let elss) = parsed_elss
else {
return Result.Error(
Error(
withMessage:
"Could not parse the else block in a conditional statement"))
}
return .Ok(
CST.ConditionalStatement(condition: condition, withThen: thenns, andElse: elss))
}
return .Ok(CST.ConditionalStatement(condition: condition, withThen: thenns))
}
}
@deriveParsableStatement
extension CST.ConditionalStatement: ParsableStatement {}
extension CST.VariableDeclarationStatement: Parsable {
public typealias C = CST.VariableDeclarationStatement
public static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.VariableDeclarationStatement> {
#RequireNodeType<Node, CST.VariableDeclarationStatement>(
node: node, type: "variableDeclaration", nice_type_name: "variable declaration statement")
let maybe_typeref = node.child(at: 0)
guard let typeref = maybe_typeref,
typeref.nodeType == "typeRef"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find type name for variable declaration statement"))
}
let maybe_variablename = node.child(at: 1)
guard let variablename = maybe_variablename,
variablename.nodeType == "identifier"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Did not find identifier name for variable declaration statement"))
}
let maybe_rvalue = node.childCount > 3 ? node.child(at: 3) : .none
guard
case .Ok(let parsed_variablename) = CST.Identifier.ParseExpression(
node: variablename, withContext: context)
else {
return Result.Error(
Error(withMessage: "Could not parse variable name"))
}
guard
case .Ok(let declaration_p4_type) = CST.Types.ParseType(type: typeref, withContext: context)
else {
return Result.Error(
Error(withMessage: "Could not parse a P4 type from \(typeref.text!)"))
}
var initializer: CST.Categories.Expression? = .none
// If there is an initializer, it must be an expression.
if let initializer_expression = maybe_rvalue {
guard initializer_expression.nodeType == "expression" else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "initial value for declaration statement is not an expression"))
}
let maybe_parsed_rvalue = CST.Expression.Parse(
node: initializer_expression, withContext: context)
guard
case .Ok(let parsed_rvalue) = maybe_parsed_rvalue
else {
return .Error(maybe_parsed_rvalue.error()!)
}
initializer = parsed_rvalue
}
return Result.Ok(
CST.VariableDeclarationStatement(
identifier: parsed_variablename as! CST.Identifier, withType: declaration_p4_type,
withInitializer: initializer),
)
}
}
@deriveParsableStatement
extension CST.VariableDeclarationStatement: ParsableStatement {}
extension CST.ExpressionStatement: Parsable {
public typealias C = CST.ExpressionStatement
public static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.ExpressionStatement> {
#RequireNodeType<Node, (P4Statement)>(
node: node, type: "expressionStatement", nice_type_name: "expression statement")
let expression_node = node.child(at: 0)!
return switch CST.Expression.Parse(node: expression_node, withContext: context) {
case .Ok(let expression): .Ok(CST.ExpressionStatement(expression))
case .Error(let e): .Error(e)
}
}
}
@deriveParsableStatement
extension CST.ExpressionStatement: ParsableStatement {}
extension CST.ParserAssignmentStatement: Parsable {
public typealias C = CST.ParserAssignmentStatement
public static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.ParserAssignmentStatement> {
#RequireNodeType<Node, CST.ParserAssignmentStatement>(
node: node, type: "assignmentStatement", nice_type_name: "assignment statement")
guard let lvalue_node = node.child(at: 0),
lvalue_node.nodeType == "expression"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing lvalue in assignment statement"))
}
guard let rvalue_node = node.child(at: 2),
rvalue_node.nodeType == "expression"
else {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Missing rvalue in assignment statement"))
}
let maybe_parsed_rvalue = CST.Expression.Parse(
node: rvalue_node, withContext: context)
guard case Result.Ok(let rvalue) = maybe_parsed_rvalue else {
return Result.Error(maybe_parsed_rvalue.error()!)
}
let maybe_parsed_lvalue = CST.Expression.Parse(node: lvalue_node, withContext: context)
guard case .Ok(let lvalue) = maybe_parsed_lvalue else {
return Result.Error(maybe_parsed_lvalue.error()!)
}
return Result.Ok(
CST.ParserAssignmentStatement(
withLValue: lvalue,
withValue: rvalue
))
}
}
@deriveParsableStatement
extension CST.ParserAssignmentStatement: ParsableStatement {}
extension CST.ReturnStatement: Parsable {
public typealias C = CST.ReturnStatement
public static func Parse(
node: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.ReturnStatement> {
#RequireNodeType<Node, CST.ReturnStatement>(
node: node, type: "return_statement", nice_type_name: "return statement")
let expression_node = node.child(at: 1)!
return switch CST.Expression.Parse(node: expression_node, withContext: context) {
case .Ok(let result): .Ok(CST.ReturnStatement(result))
case .Error(let e): .Error(e)
}
}
}
@deriveParsableStatement
extension CST.ReturnStatement: ParsableStatement {}
extension CST.ApplyStatement: Parsable {
public typealias C = CST.ApplyStatement
public static func Parse(
node: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.ApplyStatement> {
#RequireNodeType<Node, CST.ApplyStatement>(
node: node, type: "apply_statement", nice_type_name: "apply statement")
let expression_node = node.child(at: 1)!
return switch CST.BlockStatement.Parse(node: expression_node, withContext: context) {
case .Ok(let statement):
.Ok(CST.ApplyStatement(statement))
case .Error(let e): .Error(e)
}
}
}
@deriveParsableStatement
extension CST.ApplyStatement: ParsableStatement {}
extension CST.Instantiation: Parsable {
public typealias C = CST.Instantiation
public static func Parse(
node: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.Instantiation> {
let expression = node
#RequireNodeType<Node, CST.Instantiation>(
node: expression, type: "instantiation", nice_type_name: "instantiation statement")
var walker = Walker(node: expression)
var current_node: Node? = .none
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Instantiation>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing function call component")))
let maybe_instantiated_type_name = CST.Identifier.ParseExpression(
node: current_node!, withContext: context)
guard case .Ok(let instantiated_type_name) = maybe_instantiated_type_name else {
return Result.Error(maybe_instantiated_type_name.error()!)
}
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Instantiation>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing instantiation component")))
let maybe_argument_list = CST.ArgumentList.Parse(node: current_node!, withContext: context)
guard case .Ok(let arguments) = maybe_argument_list else {
return .Error(maybe_argument_list.error()!)
}
walker.next()
#MustOr(
result: current_node, thing: walker.getNext(),
or: Result<CST.Instantiation>.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing instantiation name")))
let name = CST.Identifier.ParseExpression(node: current_node!, withContext: context)
guard case .Ok(let name) = name else {
return .Error(name.error()!)
}
return .Ok(
CST.Instantiation(
named: name as! CST.Identifier, withType: instantiated_type_name as! CST.Identifier,
withArguments: arguments))
}
}
@deriveParsableStatement
extension CST.Instantiation: ParsableStatement {}
extension CST.Statements: Parsable {
public typealias C = CST.Statements
public static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.Statements> {
if node.nodeType != "statements" && node.nodeType != "parserStatements" {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Did not find expected statements"))
}
var errors: (any Errorable)? = .none
var parsed_s: [CST.Categories.Statement] = Array()
node.enumerateNamedChildren { node in
switch CST.Statement.Parse(
node: node, withContext: context)
{
case .Ok(let parsed_statement):
parsed_s.append(parsed_statement)
case .Error(let e):
errors =
if let errors = errors {
errors.append(error: e)
} else {
e
}
}
}
if let errors = errors {
return .Error(errors)
}
return Result.Ok(CST.Statements(parsed_s))
}
}
extension CST.TransitionStatement: Parsable {
public static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.State> {
guard let state_identifier = context.lexical_context_name else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Cannot parse a transition statement without the name of the containing state."
))
}
let stmts = context.lexical_context_statements
#RequireNodeType<Node, P4Statement>(
node: node, type: "parserTransitionStatement", nice_type_name: "parser transition statement"
)
guard let tse_node = node.child(at: 1),
tse_node.nodeType! == "transitionSelectionExpression"
else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Could not find transition select expression"))
}
guard let next_node = tse_node.child(at: 0) else {
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Could not find the next token in a transition selection expression"))
}
// If the next node is an identifier, we have the simple form ...
if next_node.nodeType == "identifier" {
let maybe_parsed_next_state_id = CST.Identifier.ParseExpression(
node: next_node, withContext: context)
switch maybe_parsed_next_state_id {
case .Ok(let next_state_id):
return .Ok(
CST.ParserStateDirectTransition(
name: (state_identifier),
withNextStateIdentifier: next_state_id as! CST.Identifier, withStatements: stmts))
case .Error(let e):
return .Error(e)
}
}
// We know that the next node is a select expression.
return
switch CST.SelectExpression.ParseExpression(node: next_node, withContext: context)
{
case .Ok(let tse):
.Ok(
CST.ParserStateSelectTransition(
name: state_identifier, withTransitionExpression: tse as! CST.SelectExpression,
withStatements: stmts,
)
)
case .Error(let e): .Error(e)
}
}
}
extension CST.Statement: Parsable {
public typealias C = CST.Categories.Statement
public static func Parse(
node: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Statement> {
if node.nodeType != "parserStatement" && node.nodeType != "statement" {
return Result.Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(), withError: "Missing expected parser statement")
)
}
let statement = node.child(at: 0)!
let statementParsers: [String: ParsableStatement.Type] = [
"assignmentStatement": CST.ParserAssignmentStatement.self,
"expressionStatement": CST.ExpressionStatement.self,
"variableDeclaration": CST.VariableDeclarationStatement.self,
"conditionalStatement": CST.ConditionalStatement.self,
"blockStatement": CST.BlockStatement.self,
"return_statement": CST.ReturnStatement.self,
]
guard let parser = statementParsers[statement.nodeType ?? ""] else {
return Result.Error(
ErrorWithLocation(
sourceLocation: statement.toSourceLocation(),
withError:
"Unparseable statement type (\(statement.nodeType ?? "Unknown Statement Type"))"))
}
switch parser.ParseStatement(node: statement, withContext: context) {
case Result.Ok(let parsed):
return .Ok(parsed)
case Result.Error(let e):
return .Error(
ErrorWithLocation(
sourceLocation: node.toSourceLocation(),
withError: "Failed to parse a statement element: \(e)"))
}
}
}
+99
View File
@@ -0,0 +1,99 @@
// 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 TreeSitterExtensions
import TreeSitterP4
extension P4Boolean: MaybeParsableType {
public static func MaybeParseType(
type: SwiftTreeSitter.Node, withContext: CSTCompilerContext
) -> Result<(CST.Tipe)> {
return type.nodeType == "bool"
? .Ok(CST.Tipe(P4QualifiedType(P4Boolean())))
: .Error(Error(withMessage: "Invalid parser selected for \(type.nodeType!)"))
}
}
extension P4Int: MaybeParsableType {
public static func MaybeParseType(
type: SwiftTreeSitter.Node, withContext: CSTCompilerContext
) -> Result<(CST.Tipe)> {
#RequireNodeType<Node, CST.Tipe>(node: type, type: "int_type", nice_type_name: "Integer")
var walker = Walker(node: type)
var int_node: Node? = .none
#MustOr(
result: int_node, thing: walker.getNext(),
or: Result<CST.Tipe>.Error(
ErrorWithLocation(
sourceLocation: type.toSourceLocation(),
withError: "Missing elements in int type declaration")))
// Move passed the keyword -- now see whether there is a width
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(CST.Tipe(P4QualifiedType(P4Int(BitWidth.Width(bit_width)))))
}
return .Ok(CST.Tipe(P4QualifiedType(P4Int(BitWidth.Infinite))))
}
}
extension P4String: MaybeParsableType {
public static func MaybeParseType(
type: SwiftTreeSitter.Node, withContext: CSTCompilerContext
) -> Result<(CST.Tipe)> {
return type.nodeType == "string"
? .Ok(CST.Tipe(P4QualifiedType(P4String())))
: .Error(Error(withMessage: "Invalid parser selected for \(type.nodeType!)"))
}
}
extension CST.Types: ParsableType {
public static func ParseType(
type: SwiftTreeSitter.Node, withContext context: CSTCompilerContext
) -> Result<CST.Tipe> {
#RequireNodeType<Node, CST.Tipe>(node: type, type: "typeRef", nice_type_name: "Type Reference")
let type = type.child(at: 0)!
if type.nodeType == "baseType" {
let type = type.child(at: 0)!
let base_type_parsers: [String: MaybeParsableType.Type] = [
"bool": P4Boolean.self, "int_type": P4Int.self, "string": P4String.self,
]
guard let parser = base_type_parsers[type.nodeType!] else {
return Result.Error(Error(withMessage: "No parser for type \(type.nodeType!)"))
}
switch parser.MaybeParseType(type: type, withContext: context) {
case .Ok(let type): return .Ok(type)
case .Error(let e): return .Error(e)
}
}
return Result.Error(Error(withMessage: "Type name not recognized"))
}
}
+92
View File
@@ -0,0 +1,92 @@
// 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 TreeSitterExtensions
import TreeSitterP4
/*
public protocol CompilableValue {
static func CompileValue(withValue value: String) -> Result<P4DataValue>
}
*/
public protocol MaybeParsableType {
static func MaybeParseType(
type: SwiftTreeSitter.Node, withContext: CSTCompilerContext
) -> Result<CST.Tipe>
}
public protocol ParsableType {
static func ParseType(
type: SwiftTreeSitter.Node, withContext: CSTCompilerContext
) -> Result<CST.Tipe>
}
public protocol ParsableExpression {
static func ParseExpression(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Expression>
}
public protocol Parsable<C> {
associatedtype C
static func Parse(
node: Node, withContext context: CSTCompilerContext
) -> Result<C>
}
public protocol ParsableStatement {
static func ParseStatement(
node: Node, withContext context: CSTCompilerContext
) -> Result<CST.Categories.Statement>
}
public protocol CSTVisitor<T> {
associatedtype T
// Declarations
func visit(node: CST.Control, driver: CSTVisitorDriver, context: T) -> Result<T>
func visit(node: CST.ExternDeclaration, driver: CSTVisitorDriver, context: T) -> Result<T>
func visit(node: CST.FunctionDeclaration, driver: CSTVisitorDriver, context: T) -> Result<T>
func visit(node: CST.StructDeclaration, driver: CSTVisitorDriver, context: T) -> Result<T>
func visit(node: CST.Parser, driver: CSTVisitorDriver, context: T) -> Result<T>
// Statements
func visit(node: CST.Statements, driver: CSTVisitorDriver, context: T) -> Result<T>
func visit(
node: CST.VariableDeclarationStatement, driver: CSTVisitorDriver, context: T
) -> Result<T>
func visit(node: CST.ExpressionStatement, driver: CSTVisitorDriver, context: T) -> Result<T>
// Expressions
func visit(node: CST.KeysetExpression, driver: CSTVisitorDriver, context: T) -> Result<T>
func visit(node: CST.SelectCaseExpression, driver: CSTVisitorDriver, context: T) -> Result<T>
func visit(node: CST.SelectExpression, driver: CSTVisitorDriver, context: T) -> Result<T>
func visit(node: CST.BinaryOperatorExpression, driver: CSTVisitorDriver, context: T) -> Result<T>
func visit(node: CST.Literal, driver: CSTVisitorDriver, context: T) -> Result<T>
func visit(node: CST.Identifier, driver: CSTVisitorDriver, context: T) -> Result<T>
// Parser
func visit(
node: CST.ParserStateDirectTransition, driver: CSTVisitorDriver, context: T
) -> Result<T>
func visit(node: CST.ParserStateNoTransition, driver: CSTVisitorDriver, context: T) -> Result<T>
func visit(
node: CST.ParserStateSelectTransition, driver: CSTVisitorDriver, context: T
) -> Result<T>
}
+112
View File
@@ -0,0 +1,112 @@
// 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 CSTVisitorDriver {
public init() {}
public func visit<T>(
_ elem: any CST.Categories.LanguageElement, visitor: any CSTVisitor<T>, context: T
) -> Result<T> {
return switch elem {
case let elem as CST.Categories.Expression:
visit(expression: elem, visitor: visitor, context: context)
case let elem as CST.Categories.Statement:
visit(statement: elem, visitor: visitor, context: context)
case let elem as CST.Categories.State: visit(state: elem, visitor: visitor, context: context)
case let elem as CST.Categories.Declaration:
visit(declaration: elem, visitor: visitor, context: context)
default: .Error(Error(withMessage: "AST Language Element (\(elem)) Is Not Visitable"))
}
}
func visit<T>(
declaration: any CST.Categories.Declaration, visitor: any CSTVisitor<T>, context: T
) -> Result<T> {
return switch declaration {
case let elem as CST.Control: visitor.visit(node: elem, driver: self, context: context)
case let elem as CST.ExternDeclaration:
visitor.visit(node: elem, driver: self, context: context)
case let elem as CST.FunctionDeclaration:
visitor.visit(node: elem, driver: self, context: context)
case let elem as CST.StructDeclaration:
visitor.visit(node: elem, driver: self, context: context)
case let elem as CST.Parser: visitor.visit(node: elem, driver: self, context: context)
default: .Error(Error(withMessage: "AST Declaration Element (\(declaration)) Is Not Visitable"))
}
}
func visit<T>(
expression: any CST.Categories.Expression, visitor: any CSTVisitor<T>, context: T
) -> Result<T> {
return switch expression {
case let s as CST.Identifier:
visitor.visit(node: s, driver: self, context: context)
case let s as CST.KeysetExpression:
visitor.visit(node: s, driver: self, context: context)
case let s as CST.SelectCaseExpression:
visitor.visit(node: s, driver: self, context: context)
case let e as CST.SelectExpression:
visitor.visit(node: e, driver: self, context: context)
case let e as CST.BinaryOperatorExpression:
visitor.visit(node: e, driver: self, context: context)
case let e as CST.Literal: visitor.visit(node: e, driver: self, context: context)
default: .Error(Error(withMessage: "AST Expression Element (\(expression)) Is Not Visitable"))
}
}
func visit<T>(
state: any CST.Categories.State, visitor: any CSTVisitor<T>, context: T
) -> Result<T> {
return switch state {
case let s as CST.ParserStateDirectTransition:
visitor.visit(node: s, driver: self, context: context)
case let s as CST.ParserStateNoTransition:
visitor.visit(node: s, driver: self, context: context)
case let s as CST.ParserStateSelectTransition:
visitor.visit(node: s, driver: self, context: context)
default: .Error(Error(withMessage: "AST State Element (\(state)) Is Not Visitable"))
}
}
func visit<T>(
statement: any CST.Categories.Statement, visitor: any CSTVisitor<T>, context: T
) -> Result<T> {
return switch statement {
case let s as CST.Statements: visitor.visit(node: s, driver: self, context: context)
case let s as CST.ExpressionStatement: visitor.visit(node: s, driver: self, context: context)
case let s as CST.VariableDeclarationStatement:
visitor.visit(node: s, driver: self, context: context)
case let s as CST.Parser: visitor.visit(node: s, driver: self, context: context)
default: .Error(Error(withMessage: "AST Statement Element (\(statement)) Is Not Visitable"))
}
}
public func start<T>(
program: CST.Program, visitor: any CSTVisitor<T>, context: T
) -> Result<T> {
var context = context
switch visit(statement: program.statements, visitor: visitor, context: context) {
case .Ok(let c): context = c
case .Error(let e): return .Error(e)
}
return .Ok(context)
}
}
-86
View File
@@ -1,86 +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 P4Lang
public func Call<T>(
body: (ProgramExecution) -> (Result<T>, ProgramExecution), withArguments args: ArgumentList,
withParameters params: ParameterList, inExecution execution: ProgramExecution
) -> (Result<T>, ProgramExecution) {
if case .Error(let e) = args.compatible(params) {
return (.Error(e), execution)
}
var called_execution = execution.enter_scope()
for (parameter, argument) in zip(params.parameters, args.arguments) {
let arg_idx = argument.index
let arg_value = argument.argument
//let maybe_argument_value = arg_value.evaluate(execution: called_execution)
let maybe_argument_value = called_execution.evaluator.EvaluateExpression(
arg_value, inExecution: called_execution)
guard case (.Ok(let argument_value), let updated_execution) = maybe_argument_value else {
return (
.Error(Error(withMessage: "Cannot evaluate argument \(arg_idx): \(argument)")),
called_execution.exit_scope()
)
}
called_execution = updated_execution.declare(
identifier: parameter.name, withValue: argument_value)
}
let (maybe_call_result, updated_execution) = body(called_execution)
guard case .Ok(let call_result) = maybe_call_result else {
return (.Error(maybe_call_result.error()!), updated_execution.exit_scope())
}
// Before returning, update the (in)out parameters!
var inout_scopes = updated_execution.exit_scope().scopes
for (parameter, argument) in zip(params.parameters, args.arguments) {
if let param_direction = parameter.type.direction(),
param_direction == Direction.InOut || param_direction == Direction.Out
{
// Let's make sure that it is an evaluatable l value!
guard let arg_lvalue = argument.argument as? EvaluatableLValueExpression else {
return (
.Error(Error(withMessage: "(in)out parameter argument is not lvalue")),
updated_execution.exit_scope()
)
}
guard
case .Ok(let arg_new_value) = updated_execution.scopes.lookup(identifier: parameter.name)
else {
return (
.Error(Error(withMessage: "Could not get (in)out parameter value from scope")),
updated_execution.exit_scope()
)
}
switch arg_lvalue.set(
to: arg_new_value, inScopes: inout_scopes, duringExecution: updated_execution)
{
case .Ok((let updated_scopes, _)): inout_scopes = updated_scopes
case .Error(let e): return (.Error(e), updated_execution.exit_scope())
}
}
}
return (.Ok(call_result), updated_execution.replaceScopes(inout_scopes))
}
-104
View File
@@ -1,104 +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 P4Lang
extension Control: LibraryCallable {
public typealias T = P4TableHitMissValue
public func call(
execution: Common.ProgramExecution, arguments: ArgumentList
) -> (P4TableHitMissValue, Common.ProgramExecution) {
var control_execution = execution.enter_scope()
// Add initial values to the global scope
for (name, value) in execution.getGlobalValues() {
control_execution = control_execution.declare(identifier: name, withValue: value)
}
let call_body: (ProgramExecution) -> (Result<P4TableHitMissValue>, ProgramExecution) = {
execution in
var control_execution = execution
for action in self.actions.actions {
control_execution = control_execution.declare(
identifier: action.name, withValue: P4Value(action))
}
for key in self.table.properties.keys.keys {
// Every evaluation of the key starts from an unchanged execution context.
let (key_eval, updated_execution) = key.key.evaluate(execution: control_execution)
guard case .Ok(let key_val) = key_eval else {
return (.Error(key_eval.error()!), updated_execution)
}
/// ASSUME: The first matching entry is the one to do.
/// TODO: Check whether this matches architecture.
for (val, action) in self.table.entries {
// Skip those with mismatching types.
if !val.type().eq(key_val.type()) {
continue
}
/// ASSUME: All matches are exact.
if val.eq(key_val) {
// Lookup action!
let maybe_action = updated_execution.scopes.lookup(identifier: action)
guard case .Ok(let action) = maybe_action else {
return (.Error(maybe_action.error()!), updated_execution)
}
let aaction = (action.dataValue() as! Action)
return switch aaction.evaluate(execution: updated_execution) {
case (ControlFlow.Error, let updated_execution):
(.Error(updated_execution.getError()!), updated_execution)
case (_, let updated_execution): (.Ok(P4TableHitMissValue.Hit), updated_execution)
}
}
}
}
return (.Ok(P4TableHitMissValue.Miss), control_execution)
}
switch Call(
body: call_body, withArguments: arguments, withParameters: self.parameters,
inExecution: control_execution)
{
case (.Ok(let r), let updated_execution): return (r, updated_execution)
case (.Error(let e), let updated_execution):
return (P4TableHitMissValue.Miss, updated_execution.setError(error: e))
}
}
}
extension Action: EvaluatableStatement {
public func evaluate(
execution: Common.ProgramExecution
) -> (Common.ControlFlow, Common.ProgramExecution) {
if let body = self.body {
return body.evaluate(execution: execution)
}
return (ControlFlow.Next, execution)
}
}
-536
View File
@@ -1,536 +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 P4Lang
extension SelectCaseExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
return (execution.scopes.lookup(identifier: next_state_identifier), execution)
}
public func type() -> P4QualifiedType {
return P4QualifiedType(ParserState())
}
}
extension SelectExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
switch execution.evaluator.EvaluateExpression(self.selector, inExecution: execution) {
case (.Ok(let selector_value), let updated_execution):
for sce in self.case_expressions {
if case (.Ok(let kse), let updated_execution) = updated_execution.evaluator
.EvaluateExpression(
sce.key, inExecution: updated_execution),
kse.eq(selector_value)
{
//let result = sce.evaluate(execution: updated_execution)
let result = updated_execution.evaluator.EvaluateExpression(
sce, inExecution: updated_execution)
return result
}
}
return (.Error(Error(withMessage: "No key matched the selector")), updated_execution)
case (.Error(let e), let updated_execution): return (.Error(e), updated_execution)
}
}
public func type() -> P4QualifiedType {
return P4QualifiedType(ParserState())
}
}
// Variables are evaluatable because they can be looked up by identifiers.
extension TypedIdentifier: EvaluatableExpression {
public func type() -> P4QualifiedType {
return self.type
}
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
return (execution.scopes.lookup(identifier: self), execution)
}
}
// Variables are evaluatable because they can be looked up by identifiers.
extension TypedIdentifier: EvaluatableLValueExpression {
public func set(
to: P4Value, inScopes scopes: Common.VarValueScopes,
duringExecution execution: ProgramExecution
) -> Common.Result<(Common.VarValueScopes, P4Value)> {
if case .Error(let e) = scopes.lookup(identifier: self) {
return .Error(e)
}
return .Ok((scopes.set(identifier: self, withValue: to), to))
}
public func check(
to: any Common.EvaluatableExpression, inScopes scopes: Common.VarTypeScopes
) -> Result<()> {
guard case .Ok(let type) = scopes.lookup(identifier: self) else {
return .Error(Error(withMessage: "Cannot assign to identifier not in scope"))
}
return switch type.assignableFromType(to.type()) {
case TypeCheckResults.IncompatibleTypes:
.Error(
Error(
withMessage:
"Cannot assign value with type \(to.type()) to identifier \(self) with type \(type)"))
case TypeCheckResults.ReadOnly:
.Error(
Error(
withMessage:
"Cannot assign value with type \(to.type()) to identifier \(self) that is read only"))
case TypeCheckResults.WrongDirection:
.Error(
Error(
withMessage:
"Cannot assign value with type \(to.type()) to identifier \(self) that is in parameter")
)
case TypeCheckResults.Ok: .Ok(())
}
}
}
public func binary_equal_operator_evaluator(left: P4Value, right: P4Value) -> P4DataValue {
return Map(input: left.dataValue().eq(rhs: right.dataValue())) { input in
P4BooleanValue(withValue: input)
}
}
public func binary_lt_operator_evaluator(left: P4Value, right: P4Value) -> P4DataValue {
return Map(input: left.dataValue().lt(rhs: right.dataValue())) { input in
P4BooleanValue(withValue: input)
}
}
public func binary_lte_operator_evaluator(left: P4Value, right: P4Value) -> P4DataValue {
return Map(input: left.dataValue().lte(rhs: right.dataValue())) { input in
P4BooleanValue(withValue: input)
}
}
public func binary_gt_operator_evaluator(left: P4Value, right: P4Value) -> P4DataValue {
return Map(input: left.dataValue().gt(rhs: right.dataValue())) { input in
P4BooleanValue(withValue: input)
}
}
public func binary_gte_operator_evaluator(left: P4Value, right: P4Value) -> P4DataValue {
return Map(input: left.dataValue().gte(rhs: right.dataValue())) { input in
P4BooleanValue(withValue: input)
}
}
public func binary_and_operator_evaluator(left: P4Value, right: P4Value) -> P4DataValue {
let bleft = left.dataValue() as! P4BooleanValue
let bright = right.dataValue() as! P4BooleanValue
return Map(input: bleft.access() && bright.access()) { input in
P4BooleanValue(withValue: input)
}
}
public func binary_or_operator_evaluator(left: P4Value, right: P4Value) -> P4DataValue {
let bleft = left.dataValue() as! P4BooleanValue
let bright = right.dataValue() as! P4BooleanValue
return Map(input: bleft.access() || bright.access()) { input in
P4BooleanValue(withValue: input)
}
}
public func binary_add_operator_evaluator(left: P4Value, right: P4Value) -> P4DataValue {
let ileft = left.dataValue() as! P4IntValue
let iright = right.dataValue() as! P4IntValue
return Map(input: ileft.access() + iright.access()) { input in
P4IntValue(withValue: input)
}
}
public func binary_subtract_operator_evaluator(left: P4Value, right: P4Value) -> P4DataValue {
let ileft = left.dataValue() as! P4IntValue
let iright = right.dataValue() as! P4IntValue
return Map(input: ileft.access() - iright.access()) { input in
P4IntValue(withValue: input)
}
}
public func binary_multiply_operator_evaluator(left: P4Value, right: P4Value) -> P4DataValue {
let ileft = left.dataValue() as! P4IntValue
let iright = right.dataValue() as! P4IntValue
return Map(input: ileft.access() * iright.access()) { input in
P4IntValue(withValue: input)
}
}
public func binary_divide_operator_evaluator(left: P4Value, right: P4Value) -> P4DataValue {
let ileft = left.dataValue() as! P4IntValue
let iright = right.dataValue() as! P4IntValue
return Map(input: ileft.access() / iright.access()) { input in
P4IntValue(withValue: input)
}
}
// swift-format-ignore
public typealias BinaryOperatorChecker = (EvaluatableExpression, EvaluatableExpression) -> Result<()>
public func binary_and_or_operator_checker(
left: EvaluatableExpression, right: EvaluatableExpression
) -> Result<()> {
// Check that both are Boolean-typed things!
if !(left.type().baseType().eq(rhs: P4Boolean()) && right.type().baseType().eq(rhs: P4Boolean()))
{
return .Error(Error(withMessage: "And/Or on operands with non-bool type is not allowed"))
}
return .Ok(())
}
public func binary_int_math_operator_checker(
left: EvaluatableExpression, right: EvaluatableExpression
) -> Result<()> {
// Check that both are int-typed things!
if !(left.type().baseType().eq(rhs: P4Int()) && right.type().baseType().eq(rhs: P4Int())) {
return .Error(
Error(withMessage: "Mathematical operation on operands with non-int type is not allowed"))
}
return .Ok(())
}
extension BinaryOperatorExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
let updated_execution = execution
//let maybe_evaluated_left = self.left.evaluate(execution: updated_execution)
let maybe_evaluated_left = updated_execution.evaluator.EvaluateExpression(
self.left, inExecution: updated_execution)
guard case (.Ok(let evaluated_left), let updated_execution) = maybe_evaluated_left else {
return maybe_evaluated_left
}
//let maybe_evaluated_right = self.right.evaluate(execution: updated_execution)
let maybe_evaluated_right = updated_execution.evaluator.EvaluateExpression(
self.right, inExecution: updated_execution)
guard case (.Ok(let evaluated_right), let updated_execution) = maybe_evaluated_right else {
return maybe_evaluated_right
}
return (.Ok(P4Value(self.evaluator.2(evaluated_left, evaluated_right))), updated_execution)
}
public func type() -> P4QualifiedType {
return self.evaluator.1
}
}
extension ArrayAccessExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
let updated_execution = execution
//let maybe_name = self.name.evaluate(execution: updated_execution)
let maybe_name = updated_execution.evaluator.EvaluateExpression(
self.name, inExecution: updated_execution)
guard case (.Ok(let name), let updated_execution) = maybe_name else {
return maybe_name
}
//let maybe_indexor = self.indexor.evaluate(execution: updated_execution)
let maybe_indexor = updated_execution.evaluator.EvaluateExpression(
self.indexor, inExecution: updated_execution)
guard case (.Ok(let indexor), let updated_execution) = maybe_indexor else {
return maybe_indexor
}
guard let indexor_int = indexor.dataValue() as? P4IntValue else {
return (.Error(Error(withMessage: "\(indexor) cannot index an array")), updated_execution)
}
guard let array = name.dataValue() as? P4ArrayValue else {
return (.Error(Error(withMessage: "\(name) does not name an array")), updated_execution)
}
let accessed = array.access(indexor_int.access())
return (.Ok(accessed), updated_execution)
}
public func type() -> P4QualifiedType {
return self.type.value_type()
}
}
extension ArrayAccessExpression: EvaluatableLValueExpression {
public func set(
to: P4Value, inScopes scopes: Common.VarValueScopes,
duringExecution execution: ProgramExecution
) -> Common.Result<(Common.VarValueScopes, P4Value)> {
let updated_execution = execution
//let maybe_value = self.name.evaluate(execution: updated_execution)
let maybe_value = updated_execution.evaluator.EvaluateExpression(
self.name, inExecution: updated_execution)
guard case (.Ok(let value), let updated_execution) = maybe_value else {
return .Error(
Error(withMessage: "\(self.name) cannot be evaluated: \(maybe_value.0.error()!)"))
}
guard let array_value = value.dataValue() as? P4ArrayValue else {
return Result.Error(Error(withMessage: "\(self.name) does not identify an array"))
}
// Now, get the indexor!
//let maybe_indexor_value = self.indexor.evaluate(execution: updated_execution)
let maybe_indexor_value = updated_execution.evaluator.EvaluateExpression(
self.indexor, inExecution: updated_execution)
guard case (.Ok(let indexor_value), let updated_execution) = maybe_indexor_value else {
return Result.Error(
Error(withMessage: "\(self.indexor) cannot be evaluated: \(maybe_indexor_value.0.error()!)")
)
}
guard let indexor_int = indexor_value.dataValue() as? P4IntValue else {
return Result.Error(Error(withMessage: "\(self.indexor) cannot be used to index an array"))
}
// Now we have an array and an index!
let maybe_updated_array_data_value = array_value.set(index: indexor_int.access(), to: to)
guard case .Ok(let new_array_value) = maybe_updated_array_data_value else {
return .Error(maybe_updated_array_data_value.error()!)
}
let maybe_updated_array_value = value.update(withNewValue: new_array_value)
guard case .Ok(let updated_array_value) = maybe_updated_array_value else {
return .Error(maybe_updated_array_value.error()!)
}
let array_lvalue = self.name as! EvaluatableLValueExpression
return array_lvalue.set(
to: updated_array_value, inScopes: scopes, duringExecution: updated_execution)
}
public func check(
to: any Common.EvaluatableExpression, inScopes scopes: Common.VarTypeScopes
) -> Common.Result<()> {
return switch self.type.value_type().assignableFromType(to.type()) {
case TypeCheckResults.IncompatibleTypes:
.Error(
Error(
withMessage:
"Cannot assign value of type \(to.type()) to array with values of type \(self.name.type())"
))
case TypeCheckResults.ReadOnly:
.Error(
Error(
withMessage: "Cannot assign value of type \(to.type()) to array \(self) that is read only"
))
case TypeCheckResults.WrongDirection:
.Error(
Error(
withMessage:
"Cannot assign value of type \(to.type()) to array \(self) that is in parameter"))
case TypeCheckResults.Ok:
// Now, check the type of the array itself.
switch self.name.type().assignable() {
case TypeCheckResults.ReadOnly:
.Error(Error(withMessage: "Cannot assign to array \(self) that is read only"))
case TypeCheckResults.WrongDirection:
.Error(Error(withMessage: "Cannot assign to array \(self) that is in parameter"))
case TypeCheckResults.Ok: .Ok(())
default: .Error(Error(withMessage: "Cannot assign to array \(self)"))
}
}
}
}
extension FieldAccessExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
let updated_execution = execution
//let maybe_struct = self.strct.evaluate(execution: updated_execution)
let maybe_struct = updated_execution.evaluator.EvaluateExpression(
self.strct, inExecution: updated_execution)
guard case (.Ok(let strct), let updated_execution) = maybe_struct else {
return maybe_struct
}
guard let struct_strct = strct.dataValue() as? P4StructValue else {
return (.Error(Error(withMessage: "\(strct) does not identify a struct")), updated_execution)
}
/// TODO: Create a default value?
guard let value = struct_strct.get(field: self.field) else {
return (.Error(Error(withMessage: "Missing value")), updated_execution)
}
return (.Ok(value), updated_execution)
}
public func type() -> P4QualifiedType {
return self.field.type
}
}
extension FieldAccessExpression: EvaluatableLValueExpression {
public func set(
to: P4Value, inScopes scopes: Common.VarValueScopes,
duringExecution execution: ProgramExecution
) -> Common.Result<(Common.VarValueScopes, P4Value)> {
// For purposes of documentation, assume the field access expression we are evaluating is
// (strct_id).field_id = new_field_value
// where strct_id expands to
// (identifier.field_id1.field_id2...).field_id = new_field_value
let updated_execution = execution
// First, evaluate strct_id and make sure that it names a struct.
//let maybe_value = self.strct.evaluate(execution: updated_execution)
let maybe_value = updated_execution.evaluator.EvaluateExpression(
self.strct, inExecution: updated_execution)
guard case (.Ok(let value), let updated_execution) = maybe_value else {
return .Error(
Error(withMessage: "\(self.strct) cannot be evaluated: \(maybe_value.0.error()!)"))
}
guard let struct_value = value.dataValue() as? P4StructValue else {
return .Error(Error(withMessage: "\(self.strct) does not identify a struct"))
}
// Now we know that struct_id identifies a structure value.
// Update field_id of that structure and get the new structure value.
let maybe_new_struct_data_value = struct_value.set(field: self.field, to: to)
guard case .Ok(let new_struct_data_value) = maybe_new_struct_data_value else {
return .Error(maybe_new_struct_data_value.error()!)
}
let maybe_new_struct_value = value.update(withNewValue: new_struct_data_value)
guard case .Ok(let new_struct_value) = maybe_new_struct_value else {
return .Error(maybe_new_struct_value.error()!)
}
// That new structure value should be assignable to the lvalue that is strct_id.
// We use recursion here -- ultimately finding our way to a TypedIdentifier that
// will update the scope. Pretty cool!
let struct_lvalue = self.strct as! EvaluatableLValueExpression
return struct_lvalue.set(
to: new_struct_value, inScopes: scopes, duringExecution: updated_execution)
}
public func check(
to: any Common.EvaluatableExpression, inScopes scopes: Common.VarTypeScopes
) -> Common.Result<()> {
return switch self.field.type().assignableFromType(to.type()) {
case TypeCheckResults.IncompatibleTypes:
.Error(
Error(
withMessage:
"Cannot assign value of type \(to.type()) to field \(self.field) of type \(self.type())"
))
case TypeCheckResults.ReadOnly:
.Error(
Error(
withMessage:
"Cannot assign value of type \(to.type()) to field \(self.field) that is read only"
))
case TypeCheckResults.Ok:
// Now, check the type of the struct itself.
switch self.strct.type().assignable() {
case TypeCheckResults.ReadOnly:
.Error(
Error(
withMessage: "Cannot assign to field \(self.field) of \(self.strct) that is read only"))
case TypeCheckResults.WrongDirection:
.Error(
Error(
withMessage:
"Cannot assign to field \(self.field) of \(self.strct) that is in parameter"))
case TypeCheckResults.Ok: .Ok(())
default: .Error(Error(withMessage: "Cannot assign to field \(self.field) of \(self.strct)"))
}
default: .Error(Error(withMessage: "Cannot assign to field \(self.field) of \(self.strct)"))
}
}
}
extension KeysetExpression: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
//return self.key.evaluate(execution: execution)
return execution.evaluator.EvaluateExpression(self.key, inExecution: execution)
}
public func type() -> P4QualifiedType {
return self.key.type()
}
}
extension FunctionCall: EvaluatableExpression {
public func evaluate(
execution: Common.ProgramExecution
) -> (Common.Result<P4Value>, ProgramExecution) {
let body_params:
Result<((ProgramExecution) -> (ControlFlow, ProgramExecution), ParameterList)> =
switch self.callee {
case (.some(let callee), .none):
switch callee.body {
case .some(let body):
.Ok(
(
{ (execution: ProgramExecution) -> (ControlFlow, ProgramExecution) in
return body.evaluate(execution: execution)
}, callee.params
))
case .none: .Error(Error(withMessage: "No body for called function (\(callee.name))"))
}
case (.none, .some(let callee)):
.Ok(
(
{ (execution: ProgramExecution) -> (ControlFlow, ProgramExecution) in
return callee.execute(execution: execution)
}, callee.parameters()
))
default: .Error(Error(withMessage: "No callee found for function call"))
}
guard case .Ok(let body) = body_params else {
return (.Error(body_params.error()!), execution)
}
let call_body: (ProgramExecution) -> (Result<P4Value>, ProgramExecution) = {
(execution: ProgramExecution) in
let (control_flow, updated_execution) = body.0(execution)
return switch control_flow {
case ControlFlow.Return(.some(let value)): (.Ok(value), updated_execution)
default:
(
.Error(
Error(withMessage: "No value returned from called function")),
execution
)
}
}
return Call(
body: call_body, withArguments: self.arguments, withParameters: body.1,
inExecution: execution)
}
public func type() -> P4QualifiedType {
return P4QualifiedType(self.return_type)
}
}
extension P4Value: EvaluatableExpression {
public func evaluate(execution: ProgramExecution) -> (Result<P4Value>, ProgramExecution) {
return (.Ok(self), execution)
}
}
-198
View File
@@ -1,198 +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 P4Lang
extension ParserAssignmentStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
let updated_execution = execution
//let result = self.value.evaluate(execution: updated_execution)
let result = updated_execution.evaluator.EvaluateExpression(
self.value, inExecution: updated_execution)
guard case (.Ok(let value), let updated_execution) = result else {
return (ControlFlow.Error, execution.setError(error: result.0.error()!))
}
let maybe_updated_scopes = self.lvalue.set(
to: value, inScopes: execution.scopes, duringExecution: updated_execution)
guard case Result.Ok(let updated_scopes) = maybe_updated_scopes else {
return (ControlFlow.Error, execution.setError(error: maybe_updated_scopes.error()!))
}
execution.scopes = updated_scopes.0
return (ControlFlow.Next, updated_execution)
}
}
extension ParserStateDirectTransition: EvaluatableParserState {
public func execute(
program: Common.ProgramExecution
) -> (any EvaluatableParserState, Common.ProgramExecution) {
var program = program.enter_scope()
let (control_flow, next_execution) = program.evaluator.ExecuteStatements(
statements, inExecution: program)
switch control_flow {
case .Next: program = next_execution
case .Error: return (reject, next_execution.exit_scope())
default:
return (
reject,
next_execution.exit_scope().setError(
error: Error(withMessage: "Invalid control flow (\(control_flow) in parser)"))
)
}
let res = program.scopes.lookup(identifier: get_next_state())
if case .Ok(let value) = res {
if value.type().baseType().eq(rhs: self) {
return (value.dataValue() as! EvaluatableParserState, program.exit_scope())
}
}
program = program.setError(error: res.error()!).exit_scope()
return (self, program.exit_scope())
}
public func done() -> Bool {
return false
}
public func state() -> P4Lang.ParserState {
return self
}
}
extension ParserStateNoTransition: EvaluatableParserState {
public func execute(
program: Common.ProgramExecution
) -> (any EvaluatableParserState, Common.ProgramExecution) {
return (self, program)
}
public func done() -> Bool {
return true
}
public func state() -> P4Lang.ParserState {
return self
}
}
extension ParserStateSelectTransition: EvaluatableParserState {
public func execute(
program: Common.ProgramExecution
) -> (any EvaluatableParserState, Common.ProgramExecution) {
var program = program.enter_scope()
let (control_flow, next_execution) = program.evaluator.ExecuteStatements(
statements, inExecution: program)
switch control_flow {
case .Next: program = next_execution
case .Error: return (reject, next_execution.exit_scope())
default:
return (
reject,
next_execution.exit_scope().setError(
error: Error(withMessage: "Invalid control flow (\(control_flow) in parser)"))
)
}
//switch self.selectExpression.evaluate(execution: program) {
switch program.evaluator.EvaluateExpression(self.selectExpression, inExecution: program) {
case (.Ok(let value), let program):
if value.type().baseType().eq(rhs: self) {
return (value.dataValue() as! EvaluatableParserState, program.exit_scope())
} else {
return (
self,
program.setError(
error: Error(withMessage: "Select transition transitioned to a none state"))
)
}
case (.Error(let e), let program): return (self, program.setError(error: e).exit_scope())
}
}
public func done() -> Bool {
return false
}
public func state() -> P4Lang.ParserState {
return self
}
}
extension Parser: LibraryCallable {
public typealias T = InstantiatedParserState
public func call(
execution: Common.ProgramExecution, arguments: ArgumentList
) -> (P4Lang.InstantiatedParserState, Common.ProgramExecution) {
var execution = execution.enter_scope()
execution = execution.declare(
identifier: AsInstantiatedParserState(accept.state()).state,
withValue: P4Value(accept, P4QualifiedType.ReadOnly(accept.type())))
execution = execution.declare(
identifier: AsInstantiatedParserState(reject.state()).state,
withValue: P4Value(reject, P4QualifiedType.ReadOnly(reject.type())))
// Add initial values to the global scope
for (name, value) in execution.getGlobalValues() {
execution = execution.declare(identifier: name, withValue: value)
}
// First, add every state to the scope!
for state in self.states.states {
execution = execution.declare(
identifier: state.state, withValue: P4Value(state))
}
guard let _current_state = self.findStartState(),
var current_state = _current_state as? EvaluatableParserState
else {
return (
reject, execution.setError(error: Error(withMessage: "Could not find the start state"))
)
}
let call_body: (ProgramExecution) -> (Result<P4Value>, ProgramExecution) = {
(execution: ProgramExecution) in
var current_execution = execution
// Evaluate until the state is either accept or reject.
while !current_state.done() && !current_execution.hasError() {
(current_state, current_execution) = current_state.execute(program: current_execution)
}
return (.Ok(P4Value(AsInstantiatedParserState(current_state.state()))), current_execution)
}
return
switch Call(
body: call_body, withArguments: arguments, withParameters: parameters,
inExecution: execution)
{
case (.Ok(let value), let updated_execution):
(value.dataValue() as! InstantiatedParserState, updated_execution)
case (.Error(let e), let updated_execution):
(reject, updated_execution.setError(error: Error(withMessage: "Cannot call parser: \(e)")))
}
}
}
-35
View File
@@ -1,35 +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 P4Lang
public protocol Execution {
func execute(execution: ProgramExecution) -> ProgramExecution
}
public protocol EvaluatableParserState: P4DataValue {
func execute(program: ProgramExecution) -> (EvaluatableParserState, ProgramExecution)
func done() -> Bool
func state() -> ParserState
}
/// Defines an interface for P4 components that can be invoked directly by the p4rse library user
public protocol LibraryCallable<T> {
associatedtype T
func call(execution: ProgramExecution, arguments: ArgumentList) -> (T, ProgramExecution)
}
-91
View File
@@ -1,91 +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 P4Lang
/// The runtime for a parser
public struct Runtime<U, T: LibraryCallable<U>>: CustomStringConvertible {
public var callable: T
let initialValues: VarValueScopes?
init(callable: T) {
self.callable = callable
self.initialValues = .none
}
init(callable: T, withGlobalValues initial: VarValueScopes?) {
self.callable = callable
self.initialValues = initial
}
/// Create a parser runtime from a P4 program
public static func create(
program: P4Lang.Program
) -> Result<Runtime<InstantiatedParserState, Parser>> {
return Runtime.create(program: program, withGlobalValues: .none)
}
public static func create(
program: P4Lang.Program, withGlobalValues initial: VarValueScopes?
) -> Result<Runtime<InstantiatedParserState, Parser>> {
return switch program.starting_parser() {
case .Ok(let parser):
.Ok(
P4Runtime.Runtime<InstantiatedParserState, Parser>(
callable: parser, withGlobalValues: initial))
case .Error(let error): .Error(error)
}
}
public static func create(
control: P4Lang.Control, withGlobalValues initial: VarValueScopes?
) -> Result<Runtime<P4TableHitMissValue, Control>> {
return .Ok(
P4Runtime.Runtime<P4TableHitMissValue, Control>(callable: control, withGlobalValues: initial))
}
/// Run a P4 parser with no arguments
public func run() -> Result<(U, ProgramExecution)> {
return self.run(withArguments: ArgumentList([]))
}
/// Run the P4 parser on a given packet
public func run(
withArguments arguments: ArgumentList, inExecution pe: ProgramExecution = ProgramExecution()
) -> Result<(U, ProgramExecution)> {
let npe =
if let globals = initialValues {
pe.setGlobalValues(globals)
} else {
pe
}
let (end_state, execution) = callable.call(execution: npe, arguments: arguments)
if let error = execution.getError() {
return .Error(error)
}
return .Ok((end_state, execution))
}
public var description: String {
return "Runtime:\nExecution: \(callable)"
}
}
-135
View File
@@ -1,135 +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 P4Lang
extension BlockStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
return execution.evaluator.ExecuteStatements(
self.statements, inExecution: execution
) { (cf, execution) in
switch cf {
case ControlFlow.Return(let value): return (ControlFlow.Return(value), execution)
case ControlFlow.Next: return (cf, execution)
case ControlFlow.Error: return (ControlFlow.Error, execution)
default:
return (
ControlFlow.Error,
execution.setError(
error: Error(withMessage: "Invalid control flow \(cf) in block statement"))
)
}
}
}
}
extension VariableDeclarationStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
guard
//case (.Ok(let initial_value), let execution) = self.initializer.evaluate(execution: execution)
case (.Ok(let initial_value), let execution) = execution.evaluator.EvaluateExpression(
self.initializer, inExecution: execution)
else {
return (
ControlFlow.Error,
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
return (ControlFlow.Next, execution)
}
}
extension ConditionalStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
guard
//case (.Ok(let evaluated_condition), let execution) = self.condition.evaluate(execution: execution)
case (.Ok(let evaluated_condition), let execution) = execution.evaluator.EvaluateExpression(
self.condition, inExecution: execution)
else {
return (
ControlFlow.Error,
execution.setError(error: Error(withMessage: "Could not evaluate \(self.condition)"))
)
}
if !evaluated_condition.type().baseType().eq(rhs: P4Boolean()) {
return (
ControlFlow.Error,
execution.setError(error: Error(withMessage: "Condition expression is not a Boolean"))
)
}
if evaluated_condition.dataValue().eq(rhs: P4BooleanValue.init(withValue: true)) {
let execution = execution.enter_scope()
switch self.thenn.evaluate(execution: execution) {
case (ControlFlow.Next, let result): return (ControlFlow.Next, result.exit_scope())
case (ControlFlow.Error, let result): return (ControlFlow.Error, result.exit_scope())
case (let cf, let result):
return (
ControlFlow.Next,
result.setError(
error: Error(withMessage: "Invalid control flow \(cf) in conditional statement"))
)
}
} else if let elss = self.elss {
let execution = execution.enter_scope()
switch elss.evaluate(execution: execution) {
case (ControlFlow.Next, let result): return (ControlFlow.Next, result.exit_scope())
case (ControlFlow.Error, let result): return (ControlFlow.Error, result.exit_scope())
case (let cf, let result):
return (
ControlFlow.Next,
result.setError(
error: Error(withMessage: "Invalid control flow \(cf) in conditional statement"))
)
}
}
return (ControlFlow.Next, execution)
}
}
extension ExpressionStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
// Evaluate, there might be side effects!
//return switch self.expression.evaluate(execution: execution) {
return switch execution.evaluator.EvaluateExpression(self.expression, inExecution: execution) {
case (.Ok(_), let updated_context): (ControlFlow.Next, updated_context)
case (.Error(let e), let updated_context):
(ControlFlow.Next, updated_context.setError(error: e))
}
}
}
extension ReturnStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
//return switch self.value.evaluate(execution: execution) {
return switch execution.evaluator.EvaluateExpression(self.value, inExecution: execution) {
case (.Ok(let v), let execution): (ControlFlow.Return(v), execution)
case (.Error(let e), let execution): (ControlFlow.Error, execution.setError(error: e))
}
}
}
extension ApplyStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
return (ControlFlow.Next, execution)
}
}
+1
View File
@@ -0,0 +1 @@
Success
@@ -0,0 +1,13 @@
control simple(int x, int y) {
action a(inout bool aa, int ax, inout bool ay) {
aa = false;
}
table t {
key = {
x: exact;
y: exact;
}
}
apply {
}
};
@@ -0,0 +1,9 @@
state start {
Testing ts;
ts.yesno = true;
ts.count = 5;
transition select (ts.count == 5) {
true: accept;
false: reject;
};
}
+3
View File
@@ -0,0 +1,3 @@
parser main_parser() {
#include <annotate-parser-state.p4>
}
+2
View File
@@ -0,0 +1,2 @@
bool yesno;
int count;
+4
View File
@@ -0,0 +1,4 @@
struct Testing {
#include <annotate-struct-body.p4>
};
#include <annotate-parser.p4>
@@ -0,0 +1,9 @@
state start {
Testing ts;
ts.yesno = true;
ts.count = 5;
transition select (ts.yesno == "testing") {
true: accept;
false: reject;
};
}
@@ -0,0 +1,3 @@
parser main_parser() {
#include <file-loc-error-parser-state.p4>
};
+5
View File
@@ -0,0 +1,5 @@
struct Testing {
bool yesno;
int count;
};
#include <file-loc-error-parser.p4>
@@ -0,0 +1,9 @@
state start {
Testing ts;
ts.yesno = true;
ts.count = 5;
transition select (ts.count == 5) {
true: accept;
false: reject;
};
}
+3
View File
@@ -0,0 +1,3 @@
parser main_parser() {
#include <file-loc-parser-state.p4>
}
+5
View File
@@ -0,0 +1,5 @@
struct Testing {
bool yesno;
int count;
};
#include <file-loc-parser.p4>
@@ -0,0 +1,3 @@
Error: In action-parameters-wrong-order.p4, there was an error:
...ion a(inout bool aa, int ax, inout bool ay) {...
All parameters with direction must precede directionless parameters
@@ -0,0 +1,3 @@
Error: In file-loc-error-parser-state.p4 included at position 23 in file-loc-error-parser.p4 included at position 51 in file-loc-error.p4, there was an error:
...ect (ts.yesno == "testing") {...
Could not parse transition select expression selector expression: Types of values used with binary expression are not the same
@@ -0,0 +1,8 @@
parser main_parser() {
state start {
transition select (false) {
true: reject;
false: reject;
};
}
};
@@ -0,0 +1 @@
Preprocessor Error: Could not open simple.p for preprocessing
+1
View File
@@ -0,0 +1 @@
Success
+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 Foundation
import SystemPackage
import Testing
private let TestGoldenPath: FilePath.Component = FilePath.Component("TestData")
private let TestGoldenExtension: String = ".golden"
typealias Accessor =
@convention(c) (
_ outValue: UnsafeMutableRawPointer,
_ type: UnsafeRawPointer,
_ hint: UnsafeRawPointer?,
_ reserved: UInt
) -> CBool
typealias TestContentRecord = (
kind: UInt32,
reserved1: UInt32,
accessor: Accessor?,
context: UInt,
reserved2: UInt
)
func findPathEnv() -> [URL]? {
for (key, value) in ProcessInfo.processInfo.environment {
if key == "PATH" {
return value.split(separator: ":").map { URL(filePath: "\($0)") }
}
}
return .none
}
func findInPath(_ what: String) -> URL? {
guard let bin_paths = findPathEnv() else {
return .none
}
let fm = FileManager()
for bin_path in bin_paths {
let sought = bin_path.appending(path: what)
if fm.fileExists(atPath: sought.path) {
return sought
}
}
return .none
}
func swiftPath() -> URL? {
return findInPath("swift")
}
func swiftRun(withArgs args: [String]) throws -> String? {
let path = swiftPath()!
let child = Process()
let so = Pipe()
child.standardOutput = so
child.executableURL = path
child.arguments =
[
"run",
"--ignore-lock" /* --ignore-lock needs to be early because it is a "location option". */,
"--skip-build",
] + args
try! child.run()
let output =
switch child.standardOutput {
case let so as Pipe:
String(data: try! so.fileHandleForReading.readToEnd()!, encoding: String.Encoding.ascii)
default: Optional<String>.none
}
return output
}
func swiftCliTestRunner(_ arg_gen: () -> [String], withExpected expected: String) async throws {
let args = arg_gen()
let run_output = try! swiftRun(withArgs: args)
#expect(run_output == expected)
}
func swiftCliTestRunner(_ arg_gen: () -> [String], withExpectedPath expected_path: String) async throws {
let args = arg_gen()
let run_output = try! swiftRun(withArgs: args)
let expected_source_absolute = FilePath(FileManager().currentDirectoryPath).appending(TestGoldenPath).appending(expected_path + TestGoldenExtension)
let expected_output = try! String(contentsOfFile: expected_source_absolute.string)
#expect(run_output == expected_output)
}
@attached(peer) public macro CliTest() =
#externalMacro(module: "Macros", type: "CliTestDeclarationMacro")
-256
View File
@@ -1,256 +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 Foundation
import Macros
import P4Runtime
import P4Lang
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_array_access() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool where_to = ta[1] == 2;
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
test_declarations = test_declarations.declare(identifier: Identifier(name: "ta"), withValue: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ta"),
withValue: P4Value(P4ArrayValue(withType: P4QualifiedType(P4Int()), withValue: [
P4Value(P4IntValue(withValue: 1)), P4Value(P4IntValue(withValue: 2)), P4Value(P4IntValue(withValue: 3))
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_array_access_invalid_type() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool where_to = ta[1];
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
test_declarations = test_declarations.declare(identifier: Identifier(name: "ta"), withValue: P4QualifiedType(P4Int()))
#expect(
#RequireErrorResult(
Error(
withMessage: "{49, 22}: Failed to parse a statement element: {65, 2}: ta does not name an array type"
),
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
)
}
@Test func test_array_access2() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool where_to = ta[0] == 2;
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
test_declarations = test_declarations.declare(identifier: Identifier(name: "ta"), withValue: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ta"),
withValue: P4Value(P4ArrayValue(withType: P4QualifiedType(P4Int()), withValue: [
P4Value(P4IntValue(withValue: 1)), P4Value(P4IntValue(withValue: 2)), P4Value(P4IntValue(withValue: 3))
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_array_access3() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
transition select (ta[0] == 1) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
test_declarations = test_declarations.declare(identifier: Identifier(name: "ta"), withValue: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ta"),
withValue: P4Value(P4ArrayValue(withType: P4QualifiedType(P4Int()), withValue: [
P4Value(P4IntValue(withValue: 1)), P4Value(P4IntValue(withValue: 2)), P4Value(P4IntValue(withValue: 3)),
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_array_access4() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
transition select (ta[1] == 2) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
test_declarations = test_declarations.declare(identifier: Identifier(name: "ta"), withValue: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ta"),
withValue: P4Value(P4ArrayValue(withType: P4QualifiedType(P4Int()), withValue: [
P4Value(P4IntValue(withValue: 1)), P4Value(P4IntValue(withValue: 2)), P4Value(P4IntValue(withValue: 3)),
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_array_access_nested() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
int where_to = ta[0][0];
transition select (where_to == 5) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
test_declarations = test_declarations.declare(identifier: Identifier(name: "ta"), withValue: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))))))
var test_values = VarValueScopes().enter()
let nested = P4Value(P4ArrayValue(
withType: P4QualifiedType(P4Int()),
withValue: [P4Value(P4IntValue(withValue: 5)), P4Value(P4IntValue(withValue: 2)), P4Value(P4IntValue(withValue: 3))]))
test_values = test_values.declare(
identifier: Identifier(name: "ta"),
withValue: P4Value(P4ArrayValue(withType: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))), withValue: [nested])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_array_set() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
ta[1] = 3;
bool where_to = ta[1] == 3;
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
test_declarations = test_declarations.declare(identifier: Identifier(name: "ta"), withValue: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ta"),
withValue: P4Value(P4ArrayValue(withType: P4QualifiedType(P4Int()), withValue: [
P4Value(P4IntValue(withValue: 1)), P4Value(P4IntValue(withValue: 2)), P4Value(P4IntValue(withValue: 3)),
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_array_set_nested() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
ta[0][0] = 5;
int where_to = ta[0][0];
transition select (where_to == 5) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
test_declarations = test_declarations.declare(identifier: Identifier(name: "ta"), withValue: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))))))
var test_values = VarValueScopes().enter()
let nested = P4Value(P4ArrayValue(
withType: P4QualifiedType(P4Int()),
withValue: [P4Value(P4IntValue(withValue: 1)), P4Value(P4IntValue(withValue: 2)), P4Value(P4IntValue(withValue: 3))]))
test_values = test_values.declare(
identifier: Identifier(name: "ta"),
withValue: P4Value(P4ArrayValue(withType: P4QualifiedType(P4Array(withValueType: P4QualifiedType(P4Int()))), withValue: [nested])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@@ -1,220 +0,0 @@
// p4rse, Copyright 202false, 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 P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
// And/Or binary operator tests ...
@Test func test_simple_parser_binary_operator_and() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true && true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_and2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (false && false) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_and3() async throws {
let simple = """
parser main_parser() {
state start {
transition select (false && true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_and4() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true && false) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_or() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true || true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_or2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true || false) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_or3() async throws {
let simple = """
parser main_parser() {
state start {
transition select (false || true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_or4() async throws {
let simple = """
parser main_parser() {
state start {
transition select (false || false) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_grouped() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true && (false || true)) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_grouped2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true && (false || false)) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@@ -1,296 +0,0 @@
// p4rse, Copyright 202false, 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 P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
// Bool binary operator tests ...
@Test func test_simple_parser_binary_operator_equal_bool() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true == true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_equal_not_equal_bool() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true == false) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_less_than_bool() async throws {
let simple = """
parser main_parser() {
state start {
transition select (false < true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_less_than_equal_bool() async throws {
let simple = """
parser main_parser() {
state start {
transition select (false <= true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_less_than_equal_bool2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true <= true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_greater_than_bool() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true > false) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_greater_than_equal_bool() async throws {
let simple = """
parser main_parser() {
state start {
transition select (false >= false) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_greater_than_equal_bool2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true >= false) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_less_than_bool_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true < false) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_less_than_bool_not2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true < true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_less_than_equal_bool_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true <= false) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_greater_than_bool_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select (false > true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_greater_than_bool_not2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (true > true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_greater_than_equal_bool_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select (false >= true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@@ -1,635 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
// Integer binary operator tests ...
@Test func test_simple_parser_binary_operator_equal_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (5 == 5) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_equal_not_equal_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (5 == 6) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_less_than_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (5 < 6) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_less_than_equal_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (5 <= 6) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_less_than_equal_integer2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (6 <= 6) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_greater_than_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (7 > 6) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_greater_than_equal_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (6 >= 6) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_greater_than_equal_integer2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (7 >= 6) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_less_than_integer_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select (6 < 5) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_less_than_integer_not2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (5 < 5) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_less_than_equal_integer_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select (6 <= 5) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_greater_than_integer_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select (5 > 6) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_greater_than_integer_not2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (5 > 5) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_greater_than_equal_integer_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select (5 >= 6) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
// Add Integers
@Test func test_simple_parser_binary_operator_add_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (5 + 5)) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_add_non_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (true + 5)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 16}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
@Test func test_simple_parser_binary_operator_add_non_integer2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (5 + false)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 17}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
@Test func test_simple_parser_binary_operator_add_non_integer3() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (false + false)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 21}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
// Subtract Integers
@Test func test_simple_parser_binary_operator_subtract_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (0 == (5 - 5)) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_subtract_non_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (true - 5)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 16}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
@Test func test_simple_parser_binary_operator_subtract_non_integer2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (5 - false)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 17}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
@Test func test_simple_parser_binary_operator_subtract_non_integer3() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (false - false)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 21}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
// Multiply Integers
@Test func test_simple_parser_binary_operator_multiply_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (25 == (5 * 5)) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_multiply_non_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (true * 5)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 16}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
@Test func test_simple_parser_binary_operator_multiply_non_integer2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (5 * false)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 17}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
@Test func test_simple_parser_binary_operator_multiply_non_integer3() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (false * false)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 21}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
// Divide Integers
@Test func test_simple_parser_binary_operator_divide_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (1 == (5 / 5)) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_divide_non_integer() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (true / 5)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 16}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
@Test func test_simple_parser_binary_operator_divide_non_integer2() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (5 / false)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 17}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
@Test func test_simple_parser_binary_operator_divide_non_integer3() async throws {
let simple = """
parser main_parser() {
state start {
transition select (10 == (false / false)) {
true: accept;
false: reject;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{72, 21}: Could not parse transition select expression selector expression: Mathematical operation on operands with non-int type is not allowed"
),
Program.Compile(simple)))
}
@@ -1,220 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
// String binary operator tests ...
@Test func test_simple_parser_binary_operator_equal_string() async throws {
let simple = """
parser main_parser() {
state start {
transition select ("five" == "five") {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_equal_not_equal_string() async throws {
let simple = """
parser main_parser() {
state start {
transition select ("five" == "six") {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_less_than_string() async throws {
let simple = """
parser main_parser() {
state start {
transition select ("five" < "six") {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_less_than_equal_string() async throws {
let simple = """
parser main_parser() {
state start {
transition select ("six" <= "six") {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_greater_than_string() async throws {
let simple = """
parser main_parser() {
state start {
transition select ("twelve" > "six") {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_greater_than_equal_string() async throws {
let simple = """
parser main_parser() {
state start {
transition select ("twelve" >= "six") {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_binary_operator_less_than_string_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select ("six" < "five") {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_less_than_equal_string_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select ("six" <= "five") {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_greater_than_string_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select ("five" > "six") {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_binary_operator_greater_than_equal_string_not() async throws {
let simple = """
parser main_parser() {
state start {
transition select ("five" >= "six") {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@@ -1,201 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_struct_equality_empty() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
Testing one;
Testing two;
transition select (one == two) {
true: accept;
false: reject;
};
}
};
"""
let test_declarations = VarTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
var test_types = TypeTypeScopes().enter()
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_types = test_types.declare(identifier: Identifier(name: "Testing"), withValue: struct_type)
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations, withGlobalTypes: test_types))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_struct_equality_one_empty() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
Testing one;
Testing two;
one.yesno = true;
transition select (one == two) {
true: accept;
false: reject;
};
}
};
"""
let test_declarations = VarTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
var test_types = TypeTypeScopes().enter()
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_types = test_types.declare(identifier: Identifier(name: "Testing"), withValue: struct_type)
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations, withGlobalTypes: test_types))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_struct_equality_neither_empty() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
Testing one;
Testing two;
one.yesno = true;
two.yesno = true;
transition select (one == two) {
true: accept;
false: reject;
};
}
};
"""
let test_declarations = VarTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
var test_types = TypeTypeScopes().enter()
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_types = test_types.declare(identifier: Identifier(name: "Testing"), withValue: struct_type)
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations, withGlobalTypes: test_types))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_struct_equality_neither_empty2() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
Testing one;
Testing two;
one.yesno = true;
two.yesno = true;
one.count = 5;
two.count = 5;
transition select (one == two) {
true: accept;
false: reject;
};
}
};
"""
let test_declarations = VarTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
var test_types = TypeTypeScopes().enter()
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_types = test_types.declare(identifier: Identifier(name: "Testing"), withValue: struct_type)
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations, withGlobalTypes: test_types))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_struct_equality_neither_empty3() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
Testing one;
Testing two;
one.yesno = true;
two.yesno = true;
one.count = 5;
two.count = 6;
transition select (one == two) {
true: accept;
false: reject;
};
}
};
"""
let test_declarations = VarTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
var test_types = TypeTypeScopes().enter()
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_types = test_types.declare(identifier: Identifier(name: "Testing"), withValue: struct_type)
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations, withGlobalTypes: test_types))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@@ -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 Foundation
import Macros
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Parser
@testable import P4CodeGen
@Test func test_text_serializer() async throws {
let simple_parser_declaration = """
parser main_parser(inout int x) {
state start {
true;
transition accept;
}
state start {
transition select (true) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(compile(simple_parser_declaration))
let v = CSTTextSerializer()
let c = CSTTextSerializerContext();
let vd = CSTVisitorDriver();
let result = try #UseOkResult((vd.start(program: program, visitor: v, context: c)))
let expected = """
Parser Expression
State: Direct Transition
Statements:
Expression Statement:
Literal Expression
Next State:
Identifier: accept
State: Select Transition
Select Expression:
Selector:
Literal Expression
Case Expressions:
Case Expression:
Keyset Expression:
Next State:
Identifier: accept
Case Expression:
Keyset Expression:
Next State:
Identifier: reject
"""
#expect(result.serialized == expected)
}
-82
View File
@@ -1,82 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_simple_parser_with_conditional_statement() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool x = true;
string check = "Invalid";
if (x) {
x = false;
check = "valid";
}
transition select (x) {
false: reject;
true: accept;
};
}
};
"""
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_with_conditional_statement_and_else() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool x = false;
string check = "Invalid";
if (x) {
x = false;
check = "a";
} else {
x = true;
check = "b";
}
transition select (x) {
false: reject;
true: accept;
};
}
};
"""
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
-322
View File
@@ -1,322 +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 Foundation
import Macros
import Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
import P4Lang
@testable import P4Compiler
@Test func test_simple_control_declaration() async throws {
let simple_parser_declaration = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
}
apply {
}
};
"""
let x = { (tipe: P4QualifiedType) -> Bool in
switch tipe.baseType() {
case let c as Control: c.name == "simple"
default: false
}
}
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
#expect(program.InstancesWithTypes(x).count == 1)
}
@Test func test_simple_control_declaration2() async throws {
let simple_parser_declaration = """
struct Testing {
};
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
}
apply {
}
};
control complex() {
action b() {
}
table t {
key = {
true: exact;
}
}
apply {
}
};
"""
let filter = { (tipe: P4QualifiedType) -> Bool in
switch tipe.baseType() {
case let c as Control: c.name == "simple" || c.name == "complex"
default: false
}
}
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
#expect(program.InstancesWithTypes(filter).count == 2)
}
@Test func test_simple_control_declaration_with_actions() async throws {
let simple_parser_declaration = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
actions = {
a;
}
}
apply {
}
};
"""
let x = { (tipe: P4QualifiedType) -> Bool in
switch tipe.baseType() {
case let c as Control: c.name == "simple"
default: false
}
}
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
#expect(program.InstancesWithTypes(x).count == 1)
}
@Test func test_simple_control_declaration_with_misnamed_actions() async throws {
let simple_parser_declaration = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
actions = {
b;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{54, 63}: Error(s) parsing property list: {91, 26}: Error(s) parsing table actions: Cannot find b in lexical scope."
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_misnamed_actions2() async throws {
let simple_parser_declaration = """
control simple() {
action a() {
}
table t {
key = {
true: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{54, 72}: Error(s) parsing property list: {91, 35}: Error(s) parsing table actions: Cannot find b in lexical scope."
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_mistyped_actions() async throws {
let simple_parser_declaration = """
bool a() {
return true;
};
control simple() {
table t {
key = {
true: exact;
}
actions = {
a;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{64, 63}: Error(s) parsing property list: {101, 26}: Error(s) parsing table actions: {101, 26}: a does not name an action"
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_parameters() async throws {
let simple_parser_declaration = """
control simple(bool x, bool y) {
action a() {
}
table t {
key = {
x: exact;
y: exact;
}
}
apply {
}
};
"""
#expect(#RequireOkResult(Program.Compile(simple_parser_declaration)))
}
@Test func test_simple_control_declaration_with_multiple_tables() async throws {
let simple_parser_declaration = """
control simple(bool x, bool y, bool a, bool b) {
action a() {
}
table t {
key = {
x: exact;
y: exact;
}
}
table u {
key = {
a: exact;
b: exact;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{0, 215}: More than one table in control declaration"
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_action_using_parameter() async throws {
let simple_parser_declaration = """
control simple(bool x, bool y) {
action a(int z) {
z = 5;
}
table t {
key = {
x: exact;
y: exact;
}
}
apply {
}
};
"""
#expect(#RequireOkResult(Program.Compile(simple_parser_declaration)))
}
@Test func test_simple_control_declaration_with_action_using_parameter_wrong_type() async throws {
let simple_parser_declaration = """
control simple(bool x, bool y) {
action a(int z) {
z = false;
}
table t {
key = {
x: exact;
y: exact;
}
}
apply {
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{57, 10}: Failed to parse a statement element: {57, 1}: Cannot assign value with type Boolean to identifier z with type Int"
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_simple_control_declaration_with_element_after_apply() async throws {
let simple_parser_declaration = """
control simple(bool x, bool y) {
action a(int z) {
z = false;
}
table t {
key = {
x: exact;
y: exact;
}
}
apply {
}
table x {
key = {
x: exact;
y: exact;
}
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"Could not compile the P4 program"
),
Program.Compile(simple_parser_declaration))
)
}
-384
View File
@@ -1,384 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_control_single_key() async throws {
let simple_parser_declaration = """
control simple(inout int result, bool x) {
action a() {
result = 5;
}
action b() {
result = 7;
}
table t {
key = {
x: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
// Pull the control out of the compiled program.
let controls = program.InstancesWithTypes() { (tipe: P4QualifiedType) -> Bool in
switch tipe.baseType() {
case let c as Control: c.name == "simple"
default: false
}
}
var control = ((controls[0].baseType() as P4Type) as! Control)
// Add entries to the table.
control = control.updateTable(
addEntry: (
P4Value(P4BooleanValue(withValue: true)),
TypedIdentifier(name: "a", withType: P4QualifiedType(Action()))
)
).updateTable(
addEntry: (
P4Value(P4BooleanValue(withValue: false)),
TypedIdentifier(name: "b", withType: P4QualifiedType(Action()))
))
// Set a variable in the global scope for the inout first parameter.
var global_values = VarValueScopes().enter()
global_values = global_values.declare(
identifier: Identifier(name: "result_arg"),
withValue: P4Value(
P4IntValue(withValue: 0),
P4QualifiedType(P4Int())))
let runtime = try #UseOkResult(
P4Runtime.Runtime<P4TableHitMissValue, Control>.create(control: control, withGlobalValues: global_values))
let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
withArguments: ArgumentList([
Argument(TypedIdentifier(name: "result_arg", withType: P4QualifiedType(P4Int())), atIndex: 0),
Argument(P4Value(P4BooleanValue(withValue: true)), atIndex: 1),
])))
// We expect there to be a hit.
#expect(hit_miss == P4TableHitMissValue.Hit)
// And that the proper action was invoked.
let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
#expect(result_arg.eq(P4Value(P4IntValue(withValue: 5))))
}
@Test func test_control_single_key_false() async throws {
let simple_parser_declaration = """
control simple(inout int result, bool x) {
action a() {
result = 5;
}
action b() {
result = 7;
}
table t {
key = {
x: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
// Pull the control out of the compiled program.
let controls = program.InstancesWithTypes() { (tipe: P4QualifiedType) -> Bool in
switch tipe.baseType() {
case let c as Control: c.name == "simple"
default: false
}
}
var control = ((controls[0].baseType() as P4Type) as! Control)
// Add entries to the table.
control = control.updateTable(
addEntry: (
P4Value(P4BooleanValue(withValue: true)),
TypedIdentifier(name: "a", withType: P4QualifiedType(Action()))
)
).updateTable(
addEntry: (
P4Value(P4BooleanValue(withValue: false)),
TypedIdentifier(name: "b", withType: P4QualifiedType(Action()))
))
// Set a variable in the global scope for the inout first parameter.
var global_values = VarValueScopes().enter()
global_values = global_values.declare(
identifier: Identifier(name: "result_arg"),
withValue: P4Value(
P4IntValue(withValue: 0),
P4QualifiedType(P4Int())))
let runtime = try #UseOkResult(
P4Runtime.Runtime<P4TableHitMissValue, Control>.create(control: control, withGlobalValues: global_values))
let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
withArguments: ArgumentList([
Argument(TypedIdentifier(name: "result_arg", withType: P4QualifiedType(P4Int())), atIndex: 0),
Argument(P4Value(P4BooleanValue(withValue: false)), atIndex: 1),
])))
// We expect there to be a hit.
#expect(hit_miss == P4TableHitMissValue.Hit)
// And that the proper action was invoked.
let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
#expect(result_arg.eq(P4Value(P4IntValue(withValue: 7))))
}
@Test func test_control_single_integer_key_hit() async throws {
let simple_parser_declaration = """
control simple(inout int result, int x) {
action a() {
result = 5;
}
action b() {
result = 7;
}
table t {
key = {
x: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
// Pull the control out of the compiled program.
let controls = program.InstancesWithTypes() { (tipe: P4QualifiedType) -> Bool in
switch tipe.baseType() {
case let c as Control: c.name == "simple"
default: false
}
}
var control = ((controls[0].baseType() as P4Type) as! Control)
// Add entries to the table.
control = control.updateTable(
addEntry: (
P4Value(P4IntValue(withValue: 5)),
TypedIdentifier(name: "a", withType: P4QualifiedType(Action()))
)
).updateTable(
addEntry: (
P4Value(P4IntValue(withValue: 2)),
TypedIdentifier(name: "b", withType: P4QualifiedType(Action()))
))
// Set a variable in the global scope for the inout first parameter.
var global_values = VarValueScopes().enter()
global_values = global_values.declare(
identifier: Identifier(name: "result_arg"),
withValue: P4Value(
P4IntValue(withValue: 0),
P4QualifiedType(P4Int())))
let runtime = try #UseOkResult(
P4Runtime.Runtime<P4TableHitMissValue, Control>.create(control: control, withGlobalValues: global_values))
let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
withArguments: ArgumentList([
Argument(TypedIdentifier(name: "result_arg", withType: P4QualifiedType(P4Int())), atIndex: 0),
Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 1),
])))
// We expect there to be a hit.
#expect(hit_miss == P4TableHitMissValue.Hit)
let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
#expect(result_arg.eq(P4Value(P4IntValue(withValue: 5))))
}
@Test func test_control_single_integer_key_miss() async throws {
let simple_parser_declaration = """
control simple(inout int result, int x) {
action a() {
result = 5;
}
action b() {
result = 7;
}
table t {
key = {
x: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
// Pull the control out of the compiled program.
let controls = program.InstancesWithTypes() { (tipe: P4QualifiedType) -> Bool in
switch tipe.baseType() {
case let c as Control: c.name == "simple"
default: false
}
}
var control = ((controls[0].baseType() as P4Type) as! Control)
// Add entries to the table.
control = control.updateTable(
addEntry: (
P4Value(P4IntValue(withValue: 1)),
TypedIdentifier(name: "a", withType: P4QualifiedType(Action()))
)
).updateTable(
addEntry: (
P4Value(P4IntValue(withValue: 2)),
TypedIdentifier(name: "b", withType: P4QualifiedType(Action()))
))
// Set a variable in the global scope for the inout first parameter.
var global_values = VarValueScopes().enter()
global_values = global_values.declare(
identifier: Identifier(name: "result_arg"),
withValue: P4Value(
P4IntValue(withValue: 0),
P4QualifiedType(P4Int())))
let runtime = try #UseOkResult(
P4Runtime.Runtime<P4TableHitMissValue, Control>.create(control: control, withGlobalValues: global_values))
let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
withArguments: ArgumentList([
Argument(TypedIdentifier(name: "result_arg", withType: P4QualifiedType(P4Int())), atIndex: 0),
Argument(P4Value(P4IntValue(withValue: 3)), atIndex: 1),
])))
// We expect there to be a hit.
#expect(hit_miss == P4TableHitMissValue.Miss)
let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
#expect(result_arg.eq(P4Value(P4IntValue(withValue: 0))))
}
@Test func test_control_multiple_keys() async throws {
let simple_parser_declaration = """
control simple(inout int result, bool x, int f) {
action a() {
result = 5;
}
action b() {
result = 7;
}
table t {
key = {
x: exact;
f: exact;
}
actions = {
a;
b;
}
}
apply {
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple_parser_declaration))
// Pull the control out of the compiled program.
let controls = program.InstancesWithTypes() { (tipe: P4QualifiedType) -> Bool in
switch tipe.baseType() {
case let c as Control: c.name == "simple"
default: false
}
}
var control = ((controls[0].baseType() as P4Type) as! Control)
// Add entries to the table.
control = control.updateTable(
addEntry: (
P4Value(P4BooleanValue(withValue: true)),
TypedIdentifier(name: "a", withType: P4QualifiedType(Action()))
)
).updateTable(
addEntry: (
P4Value(P4IntValue(withValue: 5)),
TypedIdentifier(name: "b", withType: P4QualifiedType(Action()))
))
// Set a variable in the global scope for the inout first parameter.
var global_values = VarValueScopes().enter()
global_values = global_values.declare(
identifier: Identifier(name: "result_arg"),
withValue: P4Value(
P4IntValue(withValue: 0),
P4QualifiedType(P4Int())))
let runtime = try #UseOkResult(
P4Runtime.Runtime<P4TableHitMissValue, Control>.create(control: control, withGlobalValues: global_values))
let (hit_miss, updated_execution) = try #UseOkResult(runtime.run(
withArguments: ArgumentList([
Argument(TypedIdentifier(name: "result_arg", withType: P4QualifiedType(P4Int())), atIndex: 0),
Argument(P4Value(P4BooleanValue(withValue: false)), atIndex: 1),
Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 2),
])))
// We expect there to be a hit.
#expect(hit_miss == P4TableHitMissValue.Hit)
// And that the proper action was invoked.
let result_arg = try #UseOkResult(updated_execution.scopes.lookup(identifier: Identifier(name: "result_arg")))
#expect(result_arg.eq(P4Value(P4IntValue(withValue: 7))))
}
-205
View File
@@ -1,205 +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 Foundation
import Macros
import P4Runtime
import P4Lang
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_struct_declaration_and_field_write() async throws {
let simple_parser_declaration = """
struct Testing {
bool yesno;
int count;
};
parser main_parser() {
state start {
Testing ts;
ts.yesno = true;
bool where_to = ts.yesno;
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_struct_declaration_and_field_write_field_read() async throws {
let simple_parser_declaration = """
struct Testing {
bool yesno;
int count;
};
parser main_parser() {
state start {
Testing ts;
ts.yesno = true;
ts.count = 5;
transition select (ts.count == 5) {
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_struct_declaration_and_field_read_defaults() async throws {
let simple_parser_declaration = """
struct Testing {
bool yesno;
int count;
};
parser main_parser() {
state start {
Testing ts;
transition select (ts.count == 0) {
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_struct_declaration_and_field_read_defaults_sc() async throws {
let simple_parser_declaration = """
struct Testing {
bool yesno;
int count;
};
parser main_parser() {
state start {
Testing ts;
transition select (ts.count) {
0: accept;
_: 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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_struct_declaration_and_field_read_defaults_sc2() async throws {
let simple_parser_declaration = """
struct Testing {
bool yesno;
int count;
};
parser main_parser() {
state start {
Testing ts;
ts.count = 1;
transition select (ts.count) {
0: accept;
_: 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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_function_declaration() async throws {
let simple_parser_declaration = """
bool functionb() {
int count;
};
"""
#expect(#RequireOkResult(Program.Compile(simple_parser_declaration)))
}
@Test func test_function_declaration_with_parameters() async throws {
let simple_parser_declaration = """
bool functionb(bool x) {
x = true;
};
"""
#expect(#RequireOkResult(Program.Compile(simple_parser_declaration)))
}
@Test func test_function_declaration_with_parameters_and_direction() async throws {
let simple_parser_declaration = """
bool functionb(in bool x, out string y, inout int z) {
x = true;
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{63, 9}: Failed to parse a statement element: {63, 1}: Cannot assign value with type Boolean to identifier x that is in parameter"
),
Program.Compile(simple_parser_declaration))
)
}
@Test func test_function_declaration_with_parameters_and_direction_struct() async throws {
let simple_parser_declaration = """
struct Testing {
bool yesno;
int count;
};
bool functionb(in Testing x, out string y, inout int z) {
x.yesno = true;
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"{120, 15}: Failed to parse a statement element: {120, 7}: Cannot assign to field yesno of x that is in parameter"
),
Program.Compile(simple_parser_declaration))
)
}
@@ -1,58 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_error_with_location_formatting() async throws {
let formatter = FormatterAnsi()
let e = ErrorWithLocation(sourceLocation: SourceLocation(1, 5), withError: "There was an error")
let formatted = e.format(formatter)
#expect(formatted == "\u{1B}[31;1m{1, 5}\u{1B}[0m: There was an error")
}
@Test func test_errors_with_location_no_formatting() async throws {
let e = ErrorWithLocation(sourceLocation: SourceLocation(1, 5), withError: "There was an error")
let e1 = ErrorWithLocation(
sourceLocation: SourceLocation(10, 5), withError: "There was another error")
let formatted = e.append(error: e1).format(FormatterPlain())
#expect(formatted == "{1, 5}: There was an error\n{10, 5}: There was another error")
}
@Test func test_errors_with_location_ansi_formatting() async throws {
let e = ErrorWithLocation(sourceLocation: SourceLocation(1, 5), withError: "There was an error")
let e1 = ErrorWithLocation(
sourceLocation: SourceLocation(10, 5), withError: "There was another error")
let formatted = e.append(error: e1).format(FormatterAnsi())
#expect(
formatted
== "\u{1B}[31;1m{1, 5}\u{1B}[0m: There was an error\n\u{1B}[31;1m{10, 5}\u{1B}[0m: There was another error"
)
}
-88
View File
@@ -1,88 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_expression_grouped_equal() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool x = (true == (1 < 4));
transition select (x) {
false: reject;
true: accept;
};
}
};
"""
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_expression_grouped_or() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool x = (false || (1 < 4));
transition select (x) {
false: reject;
true: accept;
};
}
};
"""
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_expression_grouped_and() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool x = (false && (1 < 4));
transition select (x) {
false: reject;
true: accept;
};
}
};
"""
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@@ -1,186 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_function_call_scoped_name_collision() async throws {
let simple_parser_declaration = """
bool functionb(bool c) {
return c;
};
parser main_parser() {
state start {
int c = 5;
bool b = functionb(true);
transition select (b) {
false: reject;
true: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_function_call_scoped_name_collision2() async throws {
// Test whether the assignment to c leaks out of the function call scope.
let simple_parser_declaration = """
bool functionb(bool c) {
c = true;
return c;
};
parser main_parser() {
state start {
bool c = false;
bool b = functionb(true);
transition select (c) {
false: reject;
true: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_function_call_scoped_name_collision_inout() async throws {
let simple_parser_declaration = """
bool functionb(inout bool c) {
c = true;
return c;
};
parser main_parser() {
state start {
bool c = false;
bool b = functionb(c);
transition select (c) {
false: reject;
true: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_function_call_integer_return_value() async throws {
let simple_parser_declaration = """
int functionb(int c) {
return c;
};
parser main_parser() {
state start {
int c = 5;
transition select (5 == functionb(c)) {
false: reject;
true: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_function_call_integer_return_value2() async throws {
let simple_parser_declaration = """
int functionb(int c) {
return c;
};
parser main_parser() {
state start {
int c = 5;
transition select (4 == functionb(c)) {
false: reject;
true: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_function_call_invalid_return_type() async throws {
let simple_parser_declaration = """
int functionb(int c) {
return true;
};
parser main_parser() {
state start {
int c = 5;
transition select (4 == functionb(c)) {
false: reject;
true: accept;
};
}
};
"""
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)"))
}
@@ -1,203 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_simple_parser_with_transition_select_case_nondefault_expressions() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
transition select (true) {
false: reject;
true: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_with_transition_select_case_default_expression() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
transition select (5) {
5: reject;
_: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_with_transition_select_case_default_expression2() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
transition select (1) {
5: reject;
_: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_parser_with_transition_select_case_default_expression3() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
transition select (6) {
5: reject;
6: reject;
_: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_parser_with_transition_select_case_invalid_type() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
transition select (6) {
true: reject;
6: reject;
_: accept;
};
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage:
"Error(s) parsing select cases: {81, 4}: Key expression of type Boolean is not compatible with selector type Int"
),
Program.Compile(simple_parser_declaration)))
}
@Test func test_select_expression_selection_order() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
transition select (5) {
5: reject;
5: accept;
_: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_select_expression_from_parser_parameters() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (pmtr) {
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 args = ArgumentList([
Argument(P4Value(P4BooleanValue(withValue: false)), atIndex: 1), Argument(P4Value(P4StringValue(withValue: "Testing")), atIndex: 2), Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 3),
])
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: args))
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_select_expression_from_parser_parameters2() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (imtr == 5) {
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 args = ArgumentList([
Argument(P4Value(P4BooleanValue(withValue: false)), atIndex: 1), Argument(P4Value(P4StringValue(withValue: "Testing")), atIndex: 2), Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 3),
])
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: args))
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
-124
View File
@@ -1,124 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
public struct Return5: P4FFI {
public func execute(execution: Common.ProgramExecution) -> (
Common.ControlFlow, Common.ProgramExecution
) {
return (ControlFlow.Return(P4Value(P4IntValue(withValue: 5))), execution)
}
public func parameters() -> ParameterList {
return ParameterList()
}
public func type() -> Common.P4QualifiedType {
return P4QualifiedType(
FunctionDeclaration(
named: Identifier(name: "externally"), ofType: P4QualifiedType(P4Int()),
withParameters: ParameterList(), withBody: .none?))
}
public init() {}
}
public struct Return6: P4FFI {
public func execute(execution: Common.ProgramExecution) -> (
Common.ControlFlow, Common.ProgramExecution
) {
return (ControlFlow.Return(P4Value(P4IntValue(withValue: 6))), execution)
}
public func parameters() -> ParameterList {
return ParameterList()
}
public func type() -> Common.P4QualifiedType {
return P4QualifiedType(
FunctionDeclaration(
named: Identifier(name: "externally"), ofType: P4QualifiedType(P4Int()),
withParameters: ParameterList(), withBody: .none?))
}
public init() {}
}
@Test func test_extern_function_declaration() async throws {
let simple_parser_declaration = """
extern int externally();
parser main_parser() {
state start {
int t = externally();
transition select (t == 5) {
true: accept;
false: reject;
};
}
};
"""
let externally = Return5()
let program = try! #UseOkResult(
Program.Compile(
simple_parser_declaration, withGlobalInstances: .none, withGlobalTypes: .none,
withFFIs: [externally]))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_extern_function_declaration2() async throws {
let simple_parser_declaration = """
extern int externally();
parser main_parser() {
state start {
int t = externally();
transition select (t == 5) {
true: accept;
false: reject;
};
}
};
"""
let externally = Return6()
let program = try! #UseOkResult(
Program.Compile(
simple_parser_declaration, withGlobalInstances: .none, withGlobalTypes: .none,
withFFIs: [externally]))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
-113
View File
@@ -1,113 +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 Foundation
import Macros
import P4Runtime
import P4Lang
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_statement_interloper() async throws {
let simple_parser_declaration = """
parser main_parser() {
state starts {
bool where_to = false;
int va = 5;
transition accept;
}
state start {
bool where_to = true;
where_to = true;
transition select (where_to) {
false: reject;
true: starts;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
var statements_executed: [String] = Array()
let ev = InterloperEvaluator().setStatementInterloper() { (statement, cf, execution) in
statements_executed.append("\(statement)")
}
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: ArgumentList(), inExecution: ProgramExecution(ev)))
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
#expect(statements_executed[0].hasPrefix("VariableDeclarationStatement"))
#expect(statements_executed[1].hasPrefix("ParserAssignmentStatement"))
// Moved into starts
#expect(statements_executed[2].hasPrefix("VariableDeclarationStatement"))
#expect(statements_executed[3].hasPrefix("VariableDeclarationStatement"))
}
@Test func test_expression_interloper() async throws {
let simple_parser_declaration = """
parser main_parser() {
state starts {
bool where_to = false;
int va = 5;
transition accept;
}
state start {
bool where_to = true;
where_to = true;
transition select (where_to) {
false: reject;
true: starts;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
var expressions_evaluated: [String] = Array()
let ev = InterloperEvaluator().setExpressionInterloper() { expression, result, execution in
expressions_evaluated.append("\(expression)")
}
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: ArgumentList(), inExecution: ProgramExecution(ev)))
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
#expect(expressions_evaluated[0].hasPrefix("Value: true of Boolean"))
#expect(expressions_evaluated[1].hasPrefix("Value: true of Boolean"))
#expect(expressions_evaluated[2].hasPrefix("where_to"))
#expect(expressions_evaluated[3].hasPrefix("Value: false of Boolean"))
#expect(expressions_evaluated[4].hasPrefix("KeysetExpression"))
#expect(expressions_evaluated[5].hasPrefix("Value: true of Boolean"))
#expect(expressions_evaluated[6].hasPrefix("KeysetExpression"))
#expect(expressions_evaluated[7].hasPrefix("SelectCaseExpression"))
#expect(expressions_evaluated[8].hasPrefix("SelectExpression"))
// Moved into starts
#expect(expressions_evaluated[9].hasPrefix("Value: false of Boolean"))
#expect(expressions_evaluated[10].hasPrefix("Value: 5 of Int"))
}
-184
View File
@@ -1,184 +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 Foundation
import Macros
import Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
import P4Lang
@testable import P4Compiler
@Test func test_simple_parser_syntax_error() async throws {
let simple_parser_declaration = """
parser main_parser() {
state
transition start;
}
};
"""
#expect(
#RequireErrorResult(
Error(withMessage: "Could not compile the P4 program"),
Program.Compile(simple_parser_declaration)))
}
@Test func test_simple_compilation_with_statement() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
true;
transition start;
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
#expect(parser.states.count() == 1)
let state = AsInstantiatedParserState((try! #require(parser.states.find(withIdentifier: Identifier(name: "start")))))
#expect(state.state == Identifier(name: "start"))
#expect(state.statements.count == 1)
}
@Test func test_invalid_transition_expression_keyset_expressions() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
transition select (false) {
asdf: reject;
asde: reject;
};
}
};
"""
let compilation_error = try #UseErrorResult(Program.Compile(simple_parser_declaration))
#expect(compilation_error.msg().contains("asde"))
#expect(compilation_error.msg().contains("asdf"))
}
@Test func test_simple_compiler_macro_nodetype_test() async throws {
let simple = """
parser main_parser() {
state start {
transition select (false) {
asdf: reject;
asde: reject;
};
}
};
"""
let p = try! #UseOkResult(ConfigureP4Parser())
let result = try! #require(p.parse(simple))
#expect(
#RequireErrorResult<(EvaluatableStatement, CompilerContext)>(
ErrorWithLocation(sourceLocation: SourceLocation(2, 154), withError: "Did not find assignment statement"),
ParserAssignmentStatement.Compile( // Note: Calling ParserAssignmentStatement compilation directly.
node: result.rootNode!, withContext: CompilerContext())))
}
@Test func test_simple_compiler_parser_with_parameters() async throws {
let simple = """
parser main_parser(bool pmtr) {
state start {
transition accept;
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple))
let parser = try! #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let parameters = parser.parameters
// Check that the parameters match.
#expect(parameters.parameters.count == 1)
#expect(parameters.parameters[0].name == Identifier(name: "pmtr"))
#expect(parameters.parameters[0].type.baseType().eq(rhs: P4Boolean()))
}
@Test func test_simple_compiler_parser_with_multiple_parameters() async throws {
let simple = """
parser main_parser(bool pmtr, string smtr) {
state start {
transition accept;
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple))
let parser = try! #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let parameters = parser.parameters
// Check that the parameters match.
#expect(parameters.parameters.count == 2)
#expect(parameters.parameters[0].name == Identifier(name: "pmtr"))
#expect(parameters.parameters[0].type.baseType().eq(rhs: P4Boolean()))
#expect(parameters.parameters[1].name == Identifier(name: "smtr"))
#expect(parameters.parameters[1].type.baseType().eq(rhs: P4String()))
}
@Test func test_simple_compiler_parser_with_multiple_parameters2() async throws {
let simple = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition accept;
}
};
"""
let program = try! #UseOkResult(Program.Compile(simple))
let parser = try! #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let parameters = parser.parameters
// Check that the parameters match.
#expect(parameters.parameters.count == 3)
#expect(parameters.parameters[0].name == Identifier(name: "pmtr"))
#expect(parameters.parameters[0].type.baseType().eq(rhs: P4Boolean()))
#expect(parameters.parameters[1].name == Identifier(name: "smtr"))
#expect(parameters.parameters[1].type.baseType().eq(rhs: P4String()))
#expect(parameters.parameters[2].name == Identifier(name: "imtr"))
#expect(parameters.parameters[2].type.baseType().eq(rhs: P4Int()))
}
@Test func test_simple_compiler_parser_use_parameters() async throws {
let simple = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
pmtr = true;
transition accept;
}
};
"""
#expect(#RequireOkResult(Program.Compile(simple)))
}
-173
View File
@@ -1,173 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_simple_runtime() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
true;
transition accept;
}
};
"""
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())
// We should be in the accept state.
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_runtime_to_accept() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
true;
transition 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())
// We should be in the accept state.
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_runtime_no_start_state() async throws {
let simple_parser_declaration = """
parser main_parser() {
state tart {
true;
transition reject;
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
#expect(
#RequireErrorResult<(InstantiatedParserState, ProgramExecution)>(
Error(withMessage: "Could not find the start state"),
runtime.run()))
}
@Test func test_simple_runtime_parser_with_parameters() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (pmtr) {
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 args = ArgumentList([
Argument(P4Value(P4BooleanValue(withValue: true)), atIndex: 1), Argument(P4Value(P4StringValue(withValue: "Testing")), atIndex: 2), Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 3),
])
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: args))
// We should be in the accept state.
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_runtime_parser_with_mismatched_parameter_types() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (pmtr) {
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 args = ArgumentList([
Argument(P4Value(P4BooleanValue(withValue: true)), atIndex: 1), Argument(P4Value(P4BooleanValue(withValue: false)), atIndex: 2), Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 3),
])
#expect(
#RequireErrorResult<(InstantiatedParserState, ProgramExecution)>(
Error(withMessage: "Cannot call parser: Argument 2's type (Boolean) is incompatible with the parameter type (String)"),
runtime.run(withArguments: args)))
}
@Test func test_simple_runtime_parser_with_mismatched_parameter_types2() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (pmtr) {
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 args = ArgumentList([
Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 1), Argument(P4Value(P4StringValue(withValue: "Testing")), atIndex: 2), Argument(P4Value(P4IntValue(withValue: 5)), atIndex: 3),
])
#expect(
#RequireErrorResult<(InstantiatedParserState, ProgramExecution)>(
Error(withMessage: "Cannot call parser: Argument 1's type (Int) is incompatible with the parameter type (Boolean)"),
runtime.run(withArguments: args)))
}
@Test func test_simple_runtime_parser_with_mismatched_parameter_counts() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (pmtr) {
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 args = ArgumentList([Argument(P4Value(P4BooleanValue(withValue: true)), atIndex: 0)])
#expect(
#RequireErrorResult<(InstantiatedParserState, ProgramExecution)>(
Error(withMessage: "Cannot call parser: 1 arguments found but 3 required"),
runtime.run(withArguments: args)))
}
-159
View File
@@ -1,159 +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 Foundation
import P4Lang
import Macros
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_simple_local_element_variable_declaration() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool b = false;
string s = "testing";
true;
false;
true;
transition 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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_scope() async throws {
let simple_parser_declaration = """
parser main_parser() {
state starts {
bool where_to = false;
int va = 5;
transition accept;
}
state start {
bool where_to = true;
transition select (where_to) {
false: reject;
true: starts;
};
}
};
"""
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_scope2() async throws {
let simple_parser_declaration = """
parser main_parser() {
state starts {
bool where_to = false;
int va = 5;
transition accept;
}
state start {
bool where_to = true;
where_to = false;
transition select (where_to) {
false: reject;
true: starts;
};
}
};
"""
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_simple_assignment() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool where_to = true;
string where_from = "here";
where_to = false;
where_from = "there";
transition select (where_to) {
false: reject;
true: accept;
};
}
};
"""
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_nested_declaration_assignment() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool where_to = true;
string where_from = "here";
string where_where = "here";
if (where_to) {
bool where_from = true;
if (where_from) {
where_to = false;
}
}
where_from = "there";
transition select (where_to) {
false: reject;
true: accept;
};
}
};
"""
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())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
-66
View File
@@ -1,66 +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 Foundation
import Macros
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_scope() async throws {
let s = VarTypeScope()
let s2 = s.declare(identifier: Identifier(name: "first"), withValue: P4QualifiedType(P4Int()))
let found_first = try! #require(s2.lookup(identifier: Identifier(name: "first")))
#expect(found_first.baseType().eq(rhs: P4Int()))
#expect(s2.count == 1)
}
@Test func test_scope_no_set() async throws {
var ss = VarTypeScopes().enter()
ss = ss.declare(identifier: Identifier(name: "first"), withValue: P4QualifiedType(P4Int()))
ss = ss.enter()
ss = ss.declare(identifier: Identifier(name: "second"), withValue: P4QualifiedType(P4Boolean()))
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.baseType().eq(rhs: P4Int()))
#expect(found_second.baseType().eq(rhs: P4Boolean()))
}
@Test func test_scope_set() async throws {
var ss = VarTypeScopes().enter()
let id = Identifier(name: "first")
let id_type = P4QualifiedType(P4Int())
ss = ss.declare(identifier: id, withValue: id_type)
ss = ss.enter()
ss = ss.declare(identifier: Identifier(name: "second"), withValue: P4QualifiedType(P4Boolean()))
// Change the value of `first`.
ss = ss.set(identifier: id, withValue: P4QualifiedType(P4String()))
// Verify the change!
let found = try! #UseOkResult(ss.lookup(identifier: id))
#expect(found.baseType().eq(rhs: P4String()))
}
@@ -17,9 +17,6 @@
import Foundation
import Macros
import P4Compiler
import P4Lang
import P4Runtime
import SwiftTreeSitter
import SystemPackage
import Testing
@@ -27,57 +24,50 @@ import TreeSitter
import TreeSitterP4
@testable import Common
@testable import P4Parser
@Test func test_preprocessor() async throws {
let sm = SourceManager(["./TestData/Sources/"])
let prep = SourceCodePreprocessor(sm)
let file = FilePath.init(stringLiteral: "./TestData/Sources/simple.p4")
let expected_file = FilePath.init(FileManager().currentDirectoryPath + "/" + file.string).lexicallyNormalized()
let file = FilePath.init(stringLiteral: "simple.p4")
let source = try! (#UseOkResult(prep.preprocess(file)))
let program = try! #UseOkResult(Program.Compile(source.getSource()))
#expect(#RequireOkResult((program.find_parser(withName: Identifier(name: "main_parser")))))
#expect(source.getLocations().getPath() == expected_file)
#expect(#RequireOkResult(compile(source.getSource())))
#expect(source.getLocations().getPath() == file)
}
@Test func test_preprocessor_search_for_file() async throws {
let sm = SourceManager(["./TestData/Sources/"])
let prep = SourceCodePreprocessor(sm)
let file = FilePath.init(stringLiteral: "simple.p4")
let expected_file = FilePath.init(FileManager().currentDirectoryPath + "/TestData/Sources/simple.p4")
let source = try! (#UseOkResult(prep.preprocess(file)))
let program = try! #UseOkResult(Program.Compile(source.getSource()))
#expect(#RequireOkResult((program.find_parser(withName: Identifier(name: "main_parser")))))
#expect(source.getLocations().getPath() == expected_file)
#expect(#RequireOkResult(compile(source.getSource())))
#expect(source.getLocations().getPath() == file)
}
@Test func test_preprocessor_nested_includes() async throws {
let sm = SourceManager(["./TestData/Sources/"])
let prep = SourceCodePreprocessor(sm)
let file = FilePath.init(stringLiteral: "./TestData/Sources/simple-split.p4")
let expected_file = FilePath.init(FileManager().currentDirectoryPath + "/" + file.string).lexicallyNormalized()
let file = FilePath.init(stringLiteral: "simple-split.p4")
#expect(#RequireOkResult(prep.preprocess(file)))
let source = try! (#UseOkResult(prep.preprocess(file)))
let program = try! #UseOkResult(Program.Compile(source.getSource()))
#expect(#RequireOkResult((program.find_parser(withName: Identifier(name: "main_parser")))))
#expect(source.getLocations().getPath() == expected_file)
#expect(#RequireOkResult(compile(source.getSource())))
#expect(source.getLocations().getPath() == file)
}
@Test func test_preprocessor_oneline_includes() async throws {
let sm = SourceManager(["./TestData/Sources/"])
let prep = SourceCodePreprocessor(sm)
let file = FilePath.init(stringLiteral: "./TestData/Sources/simple-split-oneline.p4")
let expected_file = FilePath.init(FileManager().currentDirectoryPath + "/" + file.string).lexicallyNormalized()
let file = FilePath.init(stringLiteral: "simple-split-oneline.p4")
#expect(#RequireOkResult(prep.preprocess(file)))
let source = try! (#UseOkResult(prep.preprocess(file)))
let program = try! #UseOkResult(Program.Compile(source.getSource()))
#expect(#RequireOkResult((program.find_parser(withName: Identifier(name: "main_parser")))))
#expect(source.getLocations().getPath() == expected_file)
#expect(#RequireOkResult(compile(source.getSource())))
#expect(source.getLocations().getPath() == file)
}
@Test func test_preprocessor_missing_file() async throws {
@@ -95,11 +85,11 @@ import TreeSitterP4
@Test func test_preprocessor_missing_included_file() async throws {
let sm = SourceManager(["./TestData/Sources/"])
let prep = SourceCodePreprocessor(sm)
let file = FilePath.init(stringLiteral: "./TestData/Sources/simple-unfound.p4")
let file = FilePath.init(stringLiteral: "simple-unfound.p4")
#expect(
#RequireErrorResult(
Error(withMessage: "Could not open unfound.p4 for inclusion"), (prep.preprocess(file))))
Error(withMessage: "Could not open unfound.p4 for preprocessing"), (prep.preprocess(file))))
}
@@ -127,7 +117,7 @@ import TreeSitterP4
@Test func test_preprocessor_oneline_includes_locations() async throws {
let sm = SourceManager(["./TestData/Sources/"])
let prep = SourceCodePreprocessor(sm)
let file = FilePath.init(stringLiteral: "./TestData/Sources/simple-split-oneline.p4")
let file = FilePath.init(stringLiteral: "simple-split-oneline.p4")
#expect(#RequireOkResult(prep.preprocess(file)))
@@ -141,21 +131,69 @@ import TreeSitterP4
@Test func test_preprocessor_nested_includes_locations() async throws {
let sm = SourceManager(["./TestData/Sources/"])
let prep = SourceCodePreprocessor(sm)
let file = FilePath.init(stringLiteral: "./TestData/Sources/nested-split.p4")
let file = FilePath.init(stringLiteral: "nested-split.p4")
#expect(#RequireOkResult(prep.preprocess(file)))
let source = try! (#UseOkResult(prep.preprocess(file)))
#expect(source.getLocations().getLocation() == SourceLocation(0..<173))
/*
#expect(
source.getLocations().getNestedLocations()[0].getNestedLocations()[0].getLocation()
== SourceLocation(27..<47))
#expect(
source.getLocations().getNestedLocations()[0].getNestedLocations()[1].getLocation()
== SourceLocation(48..<166))
*/
}
@Test func test_preprocessor_nested_includes_annotated_source() async throws {
let sm = SourceManager(["./TestData/Sources/"])
let prep = SourceCodePreprocessor(sm)
let file = FilePath.init(stringLiteral: "annotate.p4")
let expected = """
<struct Testing {
< bool yesno;
int count;>
};
<parser main_parser() {
< state start {
Testing ts;
ts.yesno = true;
ts.count = 5;
transition select (ts.count == 5) {
true: accept;
false: reject;
};
}
>
}>>
"""
#expect(#RequireOkResult(prep.preprocess(file)))
let source = try! (#UseOkResult(prep.preprocess(file)))
#expect(source.getSource(annotated: true) == expected)
}
@Test func test_preprocessor_nested_includes_get_file_location() async throws {
let sm = SourceManager(["./TestData/Sources/"], FileManager()) // Add a FileManager to get absolute paths.
let prep = SourceCodePreprocessor(sm)
let file = FilePath.init(stringLiteral: "file-loc.p4")
let source = try! (#UseOkResult(prep.preprocess(file)))
let expected_file = file
let expected_nested_file = FilePath(stringLiteral: "file-loc-parser.p4")
let expected_nested_nested_file = FilePath(stringLiteral: "file-loc-parser-state.p4")
let found_nested_files = try! #require(source.pathForLocation(78))
#expect(found_nested_files[0].path == expected_file)
#expect(found_nested_files[1].path == expected_nested_file)
#expect(found_nested_files[2].path == expected_nested_nested_file)
}
@Test func test_source_location_contains() async throws {
let outer = SourceLocation(0..<500)
-465
View File
@@ -1,465 +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 Foundation
import Macros
import P4Runtime
import P4Lang
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_field_access() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool where_to = ts.yesno;
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_declarations = test_declarations.declare(identifier: Identifier(name: "ts"), withValue: P4QualifiedType(struct_type))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ts"),
withValue: P4Value(P4StructValue(withType: struct_type, andInitializers: [
P4Value(P4BooleanValue(withValue: true)),
P4Value(P4IntValue(withValue: 5)),
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_field_access_declared() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
Testing ts;
ts.yesno = true;
bool where_to = ts.yesno;
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
var test_types = TypeTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_types = test_types.declare(identifier: Identifier(name: "Testing"), withValue: struct_type)
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: .none, withGlobalTypes: test_types))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_field_access_declared2() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
Testing ts;
ts.yesno = true;
ts.count = 5;
bool where_to = ts.yesno;
transition select (ts.count == 5) {
true: accept;
false: reject;
};
}
};
"""
var test_types = TypeTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_types = test_types.declare(identifier: Identifier(name: "Testing"), withValue: struct_type)
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: .none, withGlobalTypes: test_types))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_field_access_opp() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
bool where_to = ts.yesno;
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_declarations = test_declarations.declare(identifier: Identifier(name: "ts"), withValue: P4QualifiedType(struct_type))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ts"),
withValue: P4Value(P4StructValue(withType: struct_type, andInitializers: [
P4Value(P4BooleanValue(withValue: false)),
P4Value(P4IntValue(withValue: 5)),
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_field_access2() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
transition select (ts.count == 5) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_declarations = test_declarations.declare(identifier: Identifier(name: "ts"), withValue: P4QualifiedType(struct_type))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ts"),
withValue: P4Value(P4StructValue(withType: struct_type, andInitializers: [
P4Value(P4BooleanValue(withValue: true)),
P4Value(P4IntValue(withValue: 5)),
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_field_access2_opp() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
transition select (ts.count == 5) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_declarations = test_declarations.declare(identifier: Identifier(name: "ts"), withValue: P4QualifiedType(struct_type))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ts"),
withValue: P4Value(P4StructValue(withType: struct_type, andInitializers: [
P4Value(P4BooleanValue(withValue: true)),
P4Value(P4IntValue(withValue: 8)),
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_field_access_nested() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
int where_to = ts.ty.count;
transition select (where_to == 5) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
let ty_fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let ty_struct_type = P4Struct(withName: Identifier(name: "nested"), andFields: ty_fields)
let ts_fields = P4StructFields([P4StructFieldIdentifier(name: "ty", withType: P4QualifiedType(ty_struct_type))])
let ts_struct_type = P4Struct(withName: Identifier(name: "outer"), andFields: ts_fields)
test_declarations = test_declarations.declare(identifier: Identifier(name: "ts"), withValue: P4QualifiedType(ts_struct_type))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ts"),
withValue: P4Value(P4StructValue(
withType: ts_struct_type,
andInitializers: [
P4Value(P4StructValue(
withType: ty_struct_type,
andInitializers: [
P4Value(P4BooleanValue(withValue: true)),
P4Value(P4IntValue(withValue: 5)),
]))
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_field_write() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
ts.yesno = true;
bool where_to = ts.yesno;
transition select (where_to) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_declarations = test_declarations.declare(identifier: Identifier(name: "ts"), withValue: P4QualifiedType(struct_type))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ts"),
withValue: P4Value(P4StructValue(withType: struct_type, andInitializers: [
P4Value(P4BooleanValue(withValue: false)),
P4Value(P4IntValue(withValue: 5)),
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_field_write_invalid_type() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
ts.yesno = 5;
transition accept;
}
};
"""
var test_declarations = VarTypeScopes().enter()
let fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let struct_type = P4Struct(withName: Identifier(name: "Testing"), andFields: fields)
test_declarations = test_declarations.declare(identifier: Identifier(name: "ts"), withValue: P4QualifiedType(struct_type))
#expect(
#RequireErrorResult(
Error(
withMessage: "{49, 13}: Failed to parse a statement element: {49, 8}: Cannot assign value of type Int to field yesno of type Boolean"
),
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
)
}
@Test func test_field_write_nested() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
ts.ty.count = 5;
int where_to = ts.ty.count;
transition select (where_to == 5) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
let ty_fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let ty_struct_type = P4Struct(withName: Identifier(name: "nested"), andFields: ty_fields)
let ts_fields = P4StructFields([P4StructFieldIdentifier(name: "ty", withType: P4QualifiedType(ty_struct_type))])
let ts_struct_type = P4Struct(withName: Identifier(name: "outer"), andFields: ts_fields)
test_declarations = test_declarations.declare(identifier: Identifier(name: "ts"), withValue: P4QualifiedType(ts_struct_type))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ts"),
withValue: P4Value(P4StructValue(
withType: ts_struct_type,
andInitializers: [
P4Value(P4StructValue(
withType: ty_struct_type,
andInitializers: [
P4Value(P4BooleanValue(withValue: true)),
P4Value(P4IntValue(withValue: 7)),
]))
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_field_write_nested2() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
ts.ty.count = 3;
ts.ty.count = 5;
int where_to = ts.ty.count;
transition select (where_to == 5) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
let ty_fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let ty_struct_type = P4Struct(withName: Identifier(name: "nested"), andFields: ty_fields)
let ts_fields = P4StructFields([P4StructFieldIdentifier(name: "ty", withType: P4QualifiedType(ty_struct_type))])
let ts_struct_type = P4Struct(withName: Identifier(name: "outer"), andFields: ts_fields)
test_declarations = test_declarations.declare(identifier: Identifier(name: "ts"), withValue: P4QualifiedType(ts_struct_type))
var test_values = VarValueScopes().enter()
test_values = test_values.declare(
identifier: Identifier(name: "ts"),
withValue: P4Value(P4StructValue(
withType: ts_struct_type,
andInitializers: [
P4Value(P4StructValue(
withType: ty_struct_type,
andInitializers: [
P4Value(P4BooleanValue(withValue: true)),
P4Value(P4IntValue(withValue: 7)),
]))
])))
let program = try #UseOkResult(
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
let runtime = try #UseOkResult(P4Runtime.Runtime<InstantiatedParserState, P4Lang.Parser>.create(program: program, withGlobalValues: test_values))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_field_write_nested_invalid_type() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
ts.ty.count = false;
int where_to = ts.ty.count;
transition select (where_to == 5) {
true: accept;
false: reject;
};
}
};
"""
var test_declarations = VarTypeScopes().enter()
let ty_fields = P4StructFields([
P4StructFieldIdentifier(name: "yesno", withType: P4QualifiedType(P4Boolean())),
P4StructFieldIdentifier(name: "count", withType: P4QualifiedType(P4Int())),
])
let ty_struct_type = P4Struct(withName: Identifier(name: "nested"), andFields: ty_fields)
let ts_fields = P4StructFields([P4StructFieldIdentifier(name: "ty", withType: P4QualifiedType(ty_struct_type))])
let ts_struct_type = P4Struct(withName: Identifier(name: "outer"), andFields: ts_fields)
test_declarations = test_declarations.declare(identifier: Identifier(name: "ts"), withValue: P4QualifiedType(ts_struct_type))
#expect(
#RequireErrorResult(
Error(
withMessage: "{49, 20}: Failed to parse a statement element: {49, 11}: Cannot assign value of type Boolean to field count of type Int"
),
Program.Compile(simple_parser_declaration, withGlobalInstances: test_declarations))
)
}
-57
View File
@@ -1,57 +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 Foundation
import Macros
import P4Runtime
import P4Lang
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_style_add_format() async throws {
let red = Style(StyleColor.Red)
let bold_red = red.update(addFormat: StyleFormat.Bold)
#expect(bold_red == Style(StyleColor.Red, [StyleFormat.Bold]))
}
@Test func test_style_add_format2() async throws {
let bold_red = Style(StyleColor.Red, [StyleFormat.Bold])
let bold_underline_red = bold_red.update(addFormat: StyleFormat.Underline)
#expect(bold_underline_red == Style(StyleColor.Red, [StyleFormat.Bold, StyleFormat.Underline]))
}
@Test func test_style_remove_format() async throws {
let bold_red = Style(StyleColor.Red, [StyleFormat.Bold])
let red = bold_red.update(removeFormat: StyleFormat.Bold)
#expect(red == Style(StyleColor.Red))
}
@Test func test_style_remove_format2() async throws {
let bold_underline_red = Style(StyleColor.Red, [StyleFormat.Bold, StyleFormat.Underline])
let underline_red = bold_underline_red.update(removeFormat: StyleFormat.Bold)
#expect(underline_red == Style(StyleColor.Red, [StyleFormat.Underline]))
}
-49
View File
@@ -1,49 +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 Foundation
import Common
import Macros
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
struct NotStringConvertible {}
struct StringConvertible: CustomStringConvertible {
public var description: String {
return "CONVERTED"
}
}
@Test func test_result_type_description_not_convertible() async throws {
let result: Result<NotStringConvertible> = Result.Ok(NotStringConvertible());
#expect("\(result)" == "Ok(Tests.NotStringConvertible())")
}
@Test func test_result_type_description_convertible() async throws {
let result: Result<StringConvertible> = Result.Ok(StringConvertible());
#expect("\(result)" == "Ok: CONVERTED")
}
@Test func test_result_type_p4value_convertible() async throws {
let result = Result.Ok(P4Value(P4IntValue(withValue: 5)))
#expect("\(result)" == "Ok: Value: 5 of Int type of type Int")
}
@@ -1,58 +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 Foundation
import Macros
import P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_invalid_statements() async throws {
let ret = ReturnStatement(P4Value(P4IntValue(withValue: 5)))
let block = BlockStatement([ret])
#expect(ContainsInvalidStatements(block: block, invalids: [ReturnStatement.self]))
}
@Test func test_no_invalid_statements() async throws {
let exprs = ExpressionStatement(P4Value(P4IntValue(withValue: 5)))
let block = BlockStatement([exprs])
#expect(!ContainsInvalidStatements(block: block, invalids: [ReturnStatement.self]))
}
@Test func test_is_invalid_statement() async throws {
let ret = ReturnStatement(P4Value(P4IntValue(withValue: 5)))
#expect(ContainsInvalidStatements(statement: ret, invalids: [ReturnStatement.self]))
}
@Test func test_no_is_invalid_statement() async throws {
let exprs = ExpressionStatement(P4Value(P4IntValue(withValue: 5)))
#expect(!ContainsInvalidStatements(statement: exprs, invalids: [ReturnStatement.self]))
}

Some files were not shown because too many files have changed in this diff Show More