common, codegen: Implement Visitor And Use For CodeGen
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>
This commit is contained in:
@@ -91,6 +91,7 @@ where `<component>` is one of:
|
|||||||
5. `documentation`: For any documentation updates.
|
5. `documentation`: For any documentation updates.
|
||||||
6. `testing`: For Swift-based tests.
|
6. `testing`: For Swift-based tests.
|
||||||
7. `cli`: For Cli components.
|
7. `cli`: For Cli components.
|
||||||
|
7. `codegen`: For code generation components.
|
||||||
|
|
||||||
where `<subcomponent>` can be more free-form and `<change>` is a pithy description of the changes in the commit.
|
where `<subcomponent>` can be more free-form and `<change>` is a pithy description of the changes in the commit.
|
||||||
|
|
||||||
|
|||||||
+60
-17
@@ -18,6 +18,7 @@
|
|||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
import Common
|
import Common
|
||||||
import P4Compiler
|
import P4Compiler
|
||||||
|
import P4Runtime
|
||||||
import SystemPackage
|
import SystemPackage
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@@ -26,7 +27,7 @@ struct Cli: ParsableCommand {
|
|||||||
|
|
||||||
static let configuration = CommandConfiguration(
|
static let configuration = CommandConfiguration(
|
||||||
abstract: "P4CE compiler, interpreter and debugger.",
|
abstract: "P4CE compiler, interpreter and debugger.",
|
||||||
subcommands: [Compile.self])
|
subcommands: [Compile.self, CodeGen.self])
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CliOptions: ParsableArguments {
|
struct CliOptions: ParsableArguments {
|
||||||
@@ -50,30 +51,72 @@ extension Cli {
|
|||||||
let prep = SourceCodePreprocessor(sm)
|
let prep = SourceCodePreprocessor(sm)
|
||||||
let file = FilePath(options.path)
|
let file = FilePath(options.path)
|
||||||
|
|
||||||
let maybe_source = prep.preprocess(file)
|
let formatter: any Formattable =
|
||||||
guard case .Ok(let source) = maybe_source else {
|
|
||||||
let formatter = FormatterAnsi()
|
|
||||||
print(ErrorWithLabel("Preprocessor Error", maybe_source.error()!).format(formatter))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let maybe_program = Program.Compile(source.getSource())
|
|
||||||
guard case .Ok(_) = maybe_program else {
|
|
||||||
let formatter = FormatterAnsi()
|
|
||||||
print(ErrorWithLabel("Compiler Error", maybe_source.error()!).format(formatter))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let success_formatter: any Formattable =
|
|
||||||
if parent.plain != 0 {
|
if parent.plain != 0 {
|
||||||
FormatterPlain()
|
FormatterPlain()
|
||||||
} else {
|
} else {
|
||||||
FormatterAnsi()
|
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 = Program.Compile(source.getSource())
|
||||||
|
guard case .Ok(_) = maybe_program else {
|
||||||
|
print(ErrorWithLabel("Compiler Error", maybe_source.error()!).format(formatter))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
print(
|
print(
|
||||||
success_formatter.formatWithStyle(
|
formatter.formatWithStyle(
|
||||||
"Success", Style(StyleColor.Green, [StyleFormat.Underline])))
|
"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 = Program.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())")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,6 +73,14 @@ public func Map<T, U>(input: T, block: (T) -> U) -> U {
|
|||||||
return block(input)
|
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 =
|
@freestanding(expression) public macro RequireOkResult<T>(_: Result<T>) -> Bool =
|
||||||
#externalMacro(module: "Macros", type: "RequireResult")
|
#externalMacro(module: "Macros", type: "RequireResult")
|
||||||
@freestanding(expression) public macro RequireErrorResult<T>(
|
@freestanding(expression) public macro RequireErrorResult<T>(
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// 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 protocol Visitable {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol LanguageVisitor<Context> {
|
||||||
|
associatedtype Context
|
||||||
|
|
||||||
|
// Program
|
||||||
|
func visit(_ program: Program, _ c: VisitorContext<Context>) -> Result<VisitorContext<Context>>
|
||||||
|
|
||||||
|
// Parser
|
||||||
|
func visit(_ parser: Parser, _ c: VisitorContext<Context>) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ parser_state: InstantiatedParserState, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
|
||||||
|
// Statements
|
||||||
|
func visit(
|
||||||
|
_ variable_declaration: VariableDeclarationStatement, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ conditional: ConditionalStatement, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ block: BlockStatement, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ rtn: ReturnStatement, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ apply: ApplyStatement, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
|
||||||
|
// Expressions
|
||||||
|
func visit(
|
||||||
|
_ keyset: KeysetExpression, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ select_case: SelectCaseExpression, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ select: SelectExpression, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ array_access: ArrayAccessExpression, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ field_access: FieldAccessExpression, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ function_call: FunctionCall, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ binary_operator: BinaryOperatorExpression, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
|
||||||
|
// Declarations
|
||||||
|
func visit(_ decl: Declaration, _ c: VisitorContext<Context>) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ extern_decl: ExternDeclaration, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ func_decl: FunctionDeclaration, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
|
||||||
|
// Control
|
||||||
|
func visit(_ action: Action, _ c: VisitorContext<Context>) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ table_key_entry: TableKeyEntry, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(
|
||||||
|
_ table_property_list: TablePropertyList, _ c: VisitorContext<Context>
|
||||||
|
) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(_ table: Table, _ c: VisitorContext<Context>) -> Result<VisitorContext<Context>>
|
||||||
|
func visit(_ control: Control, _ c: VisitorContext<Context>) -> Result<VisitorContext<Context>>
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
// Witness the parts of a P4 Program that are visitable.
|
||||||
|
extension Program: Visitable {}
|
||||||
|
extension Parser: Visitable {}
|
||||||
|
extension ParserState: Visitable {}
|
||||||
|
extension VariableDeclarationStatement: Visitable {}
|
||||||
|
extension ConditionalStatement: Visitable {}
|
||||||
|
extension BlockStatement: Visitable {}
|
||||||
|
extension ReturnStatement: Visitable {}
|
||||||
|
extension ApplyStatement: Visitable {}
|
||||||
|
extension KeysetExpression: Visitable {}
|
||||||
|
extension SelectCaseExpression: Visitable {}
|
||||||
|
extension SelectExpression: Visitable {}
|
||||||
|
extension ArrayAccessExpression: Visitable {}
|
||||||
|
extension FieldAccessExpression: Visitable {}
|
||||||
|
extension FunctionCall: Visitable {}
|
||||||
|
extension BinaryOperatorExpression: Visitable {}
|
||||||
|
extension Declaration: Visitable {}
|
||||||
|
extension ExternDeclaration: Visitable {}
|
||||||
|
extension FunctionDeclaration: Visitable {}
|
||||||
|
extension Action: Visitable {}
|
||||||
|
extension TableKeyEntry: Visitable {}
|
||||||
|
extension TablePropertyList: Visitable {}
|
||||||
|
extension Table: Visitable {}
|
||||||
|
extension Control: Visitable {}
|
||||||
|
|
||||||
|
/// Context for the visiting process.
|
||||||
|
public struct VisitorContext<UserContext> {
|
||||||
|
let visitor: VisitorDriver<UserContext>
|
||||||
|
let uc: UserContext
|
||||||
|
public init(_ v: VisitorDriver<UserContext>, _ uc: UserContext) {
|
||||||
|
self.visitor = v
|
||||||
|
self.uc = uc
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getUserContext() -> UserContext {
|
||||||
|
return self.uc
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getVisitorDriver() -> VisitorDriver<UserContext> {
|
||||||
|
return self.visitor
|
||||||
|
}
|
||||||
|
|
||||||
|
public func next(uc: UserContext) -> VisitorContext<UserContext> {
|
||||||
|
return VisitorContext<UserContext>(self.visitor, uc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A driver for visiting the components of a parsed P4 program.
|
||||||
|
public struct VisitorDriver<UserContext> {
|
||||||
|
let visitor: any LanguageVisitor<UserContext>
|
||||||
|
|
||||||
|
public init(_ visitor: any LanguageVisitor<UserContext>) {
|
||||||
|
self.visitor = visitor
|
||||||
|
}
|
||||||
|
|
||||||
|
public func generateContext(uc: UserContext) -> VisitorContext<UserContext> {
|
||||||
|
return VisitorContext(self, uc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visit a `P4Type`.
|
||||||
|
public func visit(
|
||||||
|
_ t: P4Type, context: VisitorContext<UserContext>
|
||||||
|
) -> Result<VisitorContext<UserContext>> {
|
||||||
|
return switch t {
|
||||||
|
case let vv as Control: visitor.visit(vv, context)
|
||||||
|
case let vv as Parser: visitor.visit(vv, context)
|
||||||
|
default: .Error(Error(withMessage: "Could not visit type \(t)"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visit a part of a P4 program.
|
||||||
|
public func visit(
|
||||||
|
_ v: Visitable, context: VisitorContext<UserContext>
|
||||||
|
) -> Result<VisitorContext<UserContext>> {
|
||||||
|
return switch v {
|
||||||
|
case let vv as Program: visitor.visit(vv, context)
|
||||||
|
case let vv as Parser: visitor.visit(vv, context)
|
||||||
|
case let vv as InstantiatedParserState: visitor.visit(vv, context)
|
||||||
|
case let vv as VariableDeclarationStatement: visitor.visit(vv, context)
|
||||||
|
case let vv as ConditionalStatement: visitor.visit(vv, context)
|
||||||
|
case let vv as BlockStatement: visitor.visit(vv, context)
|
||||||
|
case let vv as ReturnStatement: visitor.visit(vv, context)
|
||||||
|
case let vv as ApplyStatement: visitor.visit(vv, context)
|
||||||
|
case let vv as KeysetExpression: visitor.visit(vv, context)
|
||||||
|
case let vv as SelectCaseExpression: visitor.visit(vv, context)
|
||||||
|
case let vv as SelectExpression: visitor.visit(vv, context)
|
||||||
|
case let vv as ArrayAccessExpression: visitor.visit(vv, context)
|
||||||
|
case let vv as FieldAccessExpression: visitor.visit(vv, context)
|
||||||
|
case let vv as FunctionCall: visitor.visit(vv, context)
|
||||||
|
case let vv as BinaryOperatorExpression: visitor.visit(vv, context)
|
||||||
|
case let vv as Declaration: visitor.visit(vv, context)
|
||||||
|
case let vv as ExternDeclaration: visitor.visit(vv, context)
|
||||||
|
case let vv as FunctionDeclaration: visitor.visit(vv, context)
|
||||||
|
case let vv as Action: visitor.visit(vv, context)
|
||||||
|
case let vv as TableKeyEntry: visitor.visit(vv, context)
|
||||||
|
case let vv as TablePropertyList: visitor.visit(vv, context)
|
||||||
|
case let vv as Table: visitor.visit(vv, context)
|
||||||
|
case let vv as Control: visitor.visit(vv, context)
|
||||||
|
default: .Error(Error(withMessage: "Could not visit \(v)"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the process of visiting a P4 program.
|
||||||
|
public func start(_ v: Visitable, context: UserContext) -> Result<UserContext> {
|
||||||
|
let visit_result = self.visit(v, context: VisitorContext(self, context))
|
||||||
|
return switch visit_result {
|
||||||
|
case .Ok(let vc): .Ok(vc.getUserContext())
|
||||||
|
case .Error(let e): .Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
// 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 struct Generated {
|
||||||
|
let gen: String
|
||||||
|
public init(_ base: String = "") {
|
||||||
|
self.gen = base
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getGeneratedCode() -> String {
|
||||||
|
return self.gen
|
||||||
|
}
|
||||||
|
|
||||||
|
public func append(_ a: String) -> Generated {
|
||||||
|
return Generated(self.gen + a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate code for a P4 program.
|
||||||
|
///
|
||||||
|
/// See the [P4 behavioral model](https://github.com/p4lang/behavioral-model) for
|
||||||
|
/// the format.
|
||||||
|
public struct CodeGenerator: LanguageVisitor {
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
/// Generate code.
|
||||||
|
public func codeGen(_ node: any Visitable) -> Result<Generated> {
|
||||||
|
let visitor = VisitorDriver<Generated>(self)
|
||||||
|
let generated = Generated()
|
||||||
|
return visitor.start(node, context: generated)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO: Can we generate these implementations somehow?
|
||||||
|
|
||||||
|
public typealias Context = Generated
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: Program, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
var result: Result<VisitorContext<Generated>> = Fold(
|
||||||
|
input: v.types, initial: .Ok(c.next(uc: c.getUserContext().append("[")))
|
||||||
|
) { (current, acc) in
|
||||||
|
return switch acc {
|
||||||
|
case .Ok(let acc): acc.getVisitorDriver().visit(current, context: acc)
|
||||||
|
case .Error(let e): .Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = Fold(
|
||||||
|
input: v.externs, initial: result
|
||||||
|
) { (current, acc) in
|
||||||
|
return switch acc {
|
||||||
|
case .Ok(let acc): acc.getVisitorDriver().visit(current, context: acc)
|
||||||
|
case .Error(let e): .Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = Fold(
|
||||||
|
input: v.instances, initial: result
|
||||||
|
) { (current, acc) in
|
||||||
|
return switch acc {
|
||||||
|
case .Ok(let acc): acc.getVisitorDriver().visit(current.baseType(), context: acc)
|
||||||
|
case .Error(let e): .Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.map {
|
||||||
|
.Ok($0.next(uc: $0.getUserContext().append("]")))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: Parser, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
var initial = "{"
|
||||||
|
initial += "name: \"\(v.name)\","
|
||||||
|
initial += "init_state: \"start\","
|
||||||
|
initial += "parse_states: ["
|
||||||
|
|
||||||
|
let result: Result<VisitorContext<Generated>> = Fold(
|
||||||
|
input: v.states.states, initial: .Ok(c.next(uc: c.getUserContext().append(initial)))
|
||||||
|
) { (current, acc) in
|
||||||
|
return switch acc {
|
||||||
|
case .Ok(let acc):
|
||||||
|
switch self.visit(current, acc) {
|
||||||
|
case .Ok(let gend): .Ok(gend.next(uc: gend.getUserContext().append(",")))
|
||||||
|
case .Error(let e): .Error(e)
|
||||||
|
}
|
||||||
|
case .Error(let e): .Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.map {
|
||||||
|
.Ok($0.next(uc: $0.getUserContext().append("]}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: InstantiatedParserState, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
let direct_transition_codegen = {
|
||||||
|
(
|
||||||
|
_: ParserStateDirectTransition, c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> in
|
||||||
|
return .Ok(c.next(uc: c.getUserContext().append("[]")))
|
||||||
|
}
|
||||||
|
|
||||||
|
let no_transition_codegen = {
|
||||||
|
(
|
||||||
|
_: ParserStateNoTransition, c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> in
|
||||||
|
return .Ok(c.next(uc: c.getUserContext().append("[]")))
|
||||||
|
}
|
||||||
|
|
||||||
|
let select_transition_codegen = {
|
||||||
|
(
|
||||||
|
state: ParserStateSelectTransition, c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> in
|
||||||
|
return switch self.visit(state.selectExpression, c.next(uc: c.getUserContext().append("["))) {
|
||||||
|
case .Ok(let res): .Ok(res.next(uc: res.getUserContext().append("]")))
|
||||||
|
case .Error(let e): .Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var initial = "{"
|
||||||
|
initial += "name: \"\(v.state)\","
|
||||||
|
initial += "transitions: "
|
||||||
|
|
||||||
|
let result: Result<VisitorContext<Generated>> =
|
||||||
|
switch v {
|
||||||
|
case let s as ParserStateSelectTransition:
|
||||||
|
select_transition_codegen(s, c.next(uc: c.getUserContext().append(initial)))
|
||||||
|
case let s as ParserStateDirectTransition:
|
||||||
|
direct_transition_codegen(s, c.next(uc: c.getUserContext().append(initial)))
|
||||||
|
case let s as ParserStateNoTransition:
|
||||||
|
no_transition_codegen(s, c.next(uc: c.getUserContext().append(initial)))
|
||||||
|
default:
|
||||||
|
.Error(Error(withMessage: "Could not code gen \(self)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.map {
|
||||||
|
.Ok($0.next(uc: $0.getUserContext().append("}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: VariableDeclarationStatement, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: ConditionalStatement, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: BlockStatement, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: ReturnStatement, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: ApplyStatement, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: KeysetExpression, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: SelectCaseExpression, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: SelectExpression, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c.next(uc: c.getUserContext().append("Select")))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: ArrayAccessExpression, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: FieldAccessExpression, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: FunctionCall, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: BinaryOperatorExpression, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: Declaration, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: ExternDeclaration, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: FunctionDeclaration, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: Action, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: TableKeyEntry, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: TablePropertyList, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(_ v: Table, _ c: VisitorContext<Generated>) -> Result<VisitorContext<Generated>>
|
||||||
|
{
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func visit(
|
||||||
|
_ v: Control, _ c: VisitorContext<Generated>
|
||||||
|
) -> Result<VisitorContext<Generated>> {
|
||||||
|
return .Ok(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
// 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 P4Compiler
|
||||||
|
import P4Lang
|
||||||
|
import P4Runtime
|
||||||
|
import SwiftTreeSitter
|
||||||
|
import Testing
|
||||||
|
import TreeSitter
|
||||||
|
import TreeSitterP4
|
||||||
|
|
||||||
|
@testable import P4Runtime
|
||||||
|
|
||||||
|
func shrink(_ from: String) -> String {
|
||||||
|
return from.replacing(Regex(#/\n[\s]+/#), with: "").replacing("\n", with: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func test_codegen_parser_state_direct_transition() async throws {
|
||||||
|
let simple_parser_declaration = """
|
||||||
|
parser main_parser() {
|
||||||
|
state start {
|
||||||
|
true;
|
||||||
|
transition start;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
||||||
|
let cg = try #UseOkResult(CodeGenerator().codeGen(program))
|
||||||
|
|
||||||
|
let expected = shrink(
|
||||||
|
"""
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "main_parser",
|
||||||
|
init_state: "start",
|
||||||
|
parse_states: [
|
||||||
|
{
|
||||||
|
name: "start",
|
||||||
|
transitions: []
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
""")
|
||||||
|
|
||||||
|
#expect(expected == cg.getGeneratedCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func test_codegen_parser_state_select_transition() async throws {
|
||||||
|
let simple_parser_declaration = """
|
||||||
|
parser main_parser() {
|
||||||
|
state start {
|
||||||
|
transition select (false) {
|
||||||
|
true: reject;
|
||||||
|
false: reject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
|
||||||
|
let cg = try #UseOkResult(CodeGenerator().codeGen(program))
|
||||||
|
|
||||||
|
/// TODO: Fix this test.
|
||||||
|
let expected = shrink(
|
||||||
|
"""
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "main_parser",
|
||||||
|
init_state: "start",
|
||||||
|
parse_states: [
|
||||||
|
{
|
||||||
|
name: "start",
|
||||||
|
transitions: [Select]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
""")
|
||||||
|
|
||||||
|
#expect(expected == cg.getGeneratedCode())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user