Parse Struct Declarations

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-03-27 03:33:15 -04:00
parent 5abaac2816
commit cd26d1d22c
14 changed files with 426 additions and 155 deletions
+8 -8
View File
@@ -42,16 +42,16 @@ public func ErrorOnNode(node: Node, withError error: String) -> Error {
/// Context for compilation. /// Context for compilation.
public struct CompilerContext { public struct CompilerContext {
let names: VarTypeScopes let instances: VarTypeScopes
let types: TypeTypeScopes let types: TypeTypeScopes
public init(withNames _names: VarTypeScopes) { public init(withInstances _instances: VarTypeScopes) {
names = _names instances = _instances
types = TypeTypeScopes() types = TypeTypeScopes()
} }
public init(withNames _names: VarTypeScopes, withTypes _types: TypeTypeScopes) { public init(withInstances _instances: VarTypeScopes, withTypes _types: TypeTypeScopes) {
names = _names instances = _instances
types = _types types = _types
} }
@@ -61,8 +61,8 @@ public struct CompilerContext {
/// ///
/// - Parameter names: a ``TypeScopes`` with the updated names for the newly created compiler context. /// - Parameter names: a ``TypeScopes`` with the updated names for the newly created compiler context.
/// - Returns: A new compiler context based on the current with the same types and new names. /// - Returns: A new compiler context based on the current with the same types and new names.
public func update(newNames names: VarTypeScopes) -> CompilerContext { public func update(newInstances instances: VarTypeScopes) -> CompilerContext {
return CompilerContext(withNames: names, withTypes: self.types) return CompilerContext(withInstances: instances, withTypes: self.types)
} }
/// Update a compiler context /// Update a compiler context
@@ -72,7 +72,7 @@ public struct CompilerContext {
/// - Parameter types: a ``TypeScopes`` with the updated types for the newly created compiler context. /// - Parameter types: a ``TypeScopes`` with the updated types for the newly created compiler context.
/// - Returns: A new compiler context based on the current with the same names and new types. /// - Returns: A new compiler context based on the current with the same names and new types.
public func update(newTypes types: TypeTypeScopes) -> CompilerContext { public func update(newTypes types: TypeTypeScopes) -> CompilerContext {
return CompilerContext(withNames: self.names, withTypes: types) return CompilerContext(withInstances: self.instances, withTypes: types)
} }
} }
+262
View File
@@ -0,0 +1,262 @@
// 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<(P4Type, CompilerContext)?> {
guard let node_type = node.nodeType,
node_type == "type_declaration"
else {
return .Ok(.none)
}
// Assume that it is a struct declaration
return StructDeclaration.Compile(node: node.child(at: 0)!, withContext: context)
}
}
struct StructDeclaration {
static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(P4Type, CompilerContext)?> {
var currentChildIdx = 0
var currentChildIdxSafe = 1
var currentChild: Node? = .none
guard let node_type = node.nodeType,
node_type == "struct_declaration"
else {
return Result.Error(
ErrorOnNode(node: node, withError: "Did not find a struct declaration"))
}
if node.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing elements in struct declaration"))
}
// Skip the keyword struct
currentChildIdx += 1
currentChildIdxSafe += 1
if node.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing elements in struct declaration"))
}
// The name of the struct type.
currentChild = node.child(at: currentChildIdx)
let maybe_struct_identifier = Identifier.Compile(
node: currentChild!, withContext: context)
guard case Result.Ok(let struct_identifier) = maybe_struct_identifier else {
return Result.Error(maybe_struct_identifier.error()!)
}
currentChildIdx += 1
currentChildIdxSafe += 1
// Skip the '{'
currentChildIdx += 1
currentChildIdxSafe += 1
if node.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing element of struct declaration"))
}
currentChild = node.child(at: currentChildIdx)
// If there are no fields, it will be a "}"
if currentChild!.nodeType == "}" {
let struc = P4Struct(withName: struct_identifier, andFields: P4StructFields([]))
return Result.Ok(
(
struc,
context.update(
newTypes: context.types.declare(identifier: struct_identifier, withValue: struc))
))
}
var parse_errs: [Error] = Array()
var current_context = context
var parsed_fields: [P4StructFieldIdentifier] = Array()
if currentChild!.nodeType == "struct_declaration_fields" {
currentChild!.enumerateNamedChildren { declaration_field in
print("declaration field: \(declaration_field)")
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.append(e)
}
}
}
if !parse_errs.isEmpty {
return .Error(
Error(
withMessage: "Error(s) parsing select cases: "
+ (parse_errs.map { error in
return "\(error.msg)"
}.joined(separator: ";"))))
}
let declared_struct = P4Struct(
withName: struct_identifier, andFields: P4StructFields(parsed_fields))
return .Ok(
(
declared_struct,
current_context.update(
newTypes: current_context.types.declare(
identifier: struct_identifier, withValue: declared_struct))
))
}
}
extension P4Lang.Parser: CompilableDeclaration {
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(P4Type, CompilerContext)?> {
let parser_node = node
if parser_node.nodeType != "parserDeclaration" {
return .Ok(.none)
}
var currentChildIdx = 0
var currentChildIdxSafe = 1
var currentChild: Node? = .none
if parser_node.childCount < currentChildIdxSafe {
return .Error(
ErrorOnNode(node: parser_node, withError: "Missing elements of parser declaration"))
}
currentChild = parser_node.child(at: currentChildIdx)
if currentChild!.nodeType != "parserType" {
return .Error(
ErrorOnNode(node: currentChild!, withError: "Missing type for parser declaration"))
}
let type_node = currentChild
var parser_name: Common.Identifier? = .none
do {
// Parse the parser type (type_node)
var currentChildIdx = 0
var currentChildIdxSafe = 1
if type_node!.childCount < currentChildIdxSafe {
return .Error(
ErrorOnNode(
node: parser_node, withError: "Missing elements of parser type in parser declaration"))
}
var currentChild = type_node!.child(at: currentChildIdx)
if currentChild!.nodeType == "annotations" {
return .Error(
ErrorOnNode(
node: currentChild!, withError: "Annotations in parser type are not yet handled."))
// Will increment indexes here.
}
// Skip the parser keyword
currentChildIdx += 1
currentChildIdxSafe += 1
if type_node!.childCount < currentChildIdxSafe {
return .Error(
ErrorOnNode(node: type_node!, withError: "Missing name in parser type declaration"))
}
currentChild = type_node?.child(at: currentChildIdx)
switch Identifier.Compile(node: currentChild!, withContext: context) {
case .Ok(let id): parser_name = id
case .Error(let e):
return .Error(e)
}
}
currentChildIdx += 1
currentChildIdxSafe += 1
if parser_node.childCount < currentChildIdxSafe {
return .Error(
ErrorOnNode(node: parser_node, withError: "Missing elements of parser declaration"))
}
if currentChild!.nodeType == "constructorParameters" {
return .Error(
ErrorOnNode(node: currentChild!, withError: "Constructor parameters are not yet handled.")
)
// Will increment indexes here.
}
// Skip the '{'
currentChildIdx += 1
currentChildIdxSafe += 1
if parser_node.childCount < currentChildIdxSafe {
return .Error((Error(withMessage: "Missing body of parser declaration")))
}
currentChild = parser_node.child(at: currentChildIdx)
if currentChild!.nodeType == "parserLocalElements" {
return .Error(
ErrorOnNode(node: currentChild!, withError: "Parser Local Elements are not yet handled."))
// Will increment indexes here.
}
if parser_node.childCount < currentChildIdxSafe {
return .Error((Error(withMessage: "Missing body of parser declaration")))
}
if currentChild!.nodeType != "parserStates" {
return .Error(Error(withMessage: "Missing parser states in parser declaration"))
}
switch Parser.Compile(
withName: parser_name!, node: currentChild!, withContext: context)
{
case Result.Ok((let parser, let updated_context)):
// Create a new context with the name of the parser that was just compiled in scope.
return .Ok(
(
parser,
context.update(
newInstances: updated_context.instances.declare(
identifier: parser.name, withValue: parser))
))
case Result.Error(let error): return .Error(error)
}
// Assume that there is only '}' after -- the parser guaranteed that for us!
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ extension TypedIdentifier: CompilableExpression {
node: node, type: "identifier") node: node, type: "identifier")
guard guard
case Result.Ok(let type) = context.names.lookup( case Result.Ok(let type) = context.instances.lookup(
identifier: Common.Identifier(name: node.text!)) identifier: Common.Identifier(name: node.text!))
else { else {
return .Error(ErrorOnNode(node: node, withError: "Cannot find \(node.text!) in scope")) return .Error(ErrorOnNode(node: node, withError: "Cannot find \(node.text!) in scope"))
+2 -1
View File
@@ -284,7 +284,8 @@ public struct Parser {
// Parse a state in a nested scope. // Parse a state in a nested scope.
switch Parser.State.Compile( switch Parser.State.Compile(
node: parser_state, withContext: context.update(newNames: current_context.names.enter())) node: parser_state,
withContext: context.update(newInstances: current_context.instances.enter()))
{ {
case Result.Ok(let (state, updated_context)): case Result.Ok(let (state, updated_context)):
parser.states = parser.states.append(state: state) parser.states = parser.states.append(state: state)
+31 -123
View File
@@ -54,13 +54,14 @@ public struct Program {
var program = P4Lang.Program() var program = P4Lang.Program()
// Set up a context for parsing. // Set up a context for parsing.
var compilation_context = CompilerContext(withNames: VarTypeScopes().enter()) var compilation_context = CompilerContext(
withInstances: VarTypeScopes().enter(), withTypes: TypeTypeScopes().enter())
var errors: [Error] = Array() var errors: [Error] = Array()
// If the caller gave any global instances, add them here. // If the caller gave any global instances, add them here.
if let globalInstances = globalInstances { if let globalInstances = globalInstances {
compilation_context = compilation_context.update(newNames: globalInstances) compilation_context = compilation_context.update(newInstances: globalInstances)
} }
// If the caller gave any global types, add them here. // If the caller gave any global types, add them here.
@@ -68,135 +69,36 @@ public struct Program {
compilation_context = compilation_context.update(newTypes: globalTypes) compilation_context = compilation_context.update(newTypes: globalTypes)
} }
result?.rootNode?.enumerateNamedChildren { declaration_node in // Try to parse all top-level declarations.
if declaration_node.nodeType != "declaration" { result?.rootNode?.enumerateNamedChildren { (declaration_node: Node) in
return let specific_declaration_node = declaration_node.child(at: 0)!
}
let parser_node = declaration_node.child(at: 0)! let declaration_parsers: [CompilableDeclaration.Type] = [
if parser_node.nodeType != "parserDeclaration" { Declaration.self, P4Lang.Parser.self,
return ]
} var found_parser = false
var currentChildIdx = 0 for parser in declaration_parsers {
var currentChildIdxSafe = 1 switch parser.Compile(node: specific_declaration_node, withContext: compilation_context) {
var currentChild: Node? = .none case .Ok(.none): {}()
case .Ok(.some((_, let updated_context))):
if parser_node.childCount < currentChildIdxSafe { found_parser = true
errors.append( compilation_context = updated_context
ErrorOnNode(node: parser_node, withError: "Missing elements of parser declaration")) break
return
}
currentChild = parser_node.child(at: currentChildIdx)
if currentChild!.nodeType != "parserType" {
errors.append(
ErrorOnNode(node: currentChild!, withError: "Missing type for parser declaration"))
return
}
let type_node = currentChild
var parser_name: Common.Identifier? = .none
do {
// Parse the parser type (type_node)
var currentChildIdx = 0
var currentChildIdxSafe = 1
if type_node!.childCount < currentChildIdxSafe {
errors.append(
ErrorOnNode(
node: parser_node, withError: "Missing elements of parser type in parser declaration")
)
return
}
var currentChild = type_node!.child(at: currentChildIdx)
if currentChild!.nodeType == "annotations" {
errors.append(
ErrorOnNode(
node: currentChild!, withError: "Annotations in parser type are not yet handled."))
return
// Will increment indexes here.
}
// Skip the parser keyword
currentChildIdx += 1
currentChildIdxSafe += 1
if type_node!.childCount < currentChildIdxSafe {
errors.append(
ErrorOnNode(node: type_node!, withError: "Missing name in parser type declaration"))
return
}
currentChild = type_node?.child(at: currentChildIdx)
switch Identifier.Compile(node: currentChild!, withContext: compilation_context) {
case .Ok(let id): parser_name = id
case .Error(let e): case .Error(let e):
found_parser = true
errors.append(e) errors.append(e)
return break
} }
} }
// It's an error if there is no parser name. // If none of the declaration parsers chose to parse, that's an error, too!
if parser_name == .none { if !found_parser {
return
}
currentChildIdx += 1
currentChildIdxSafe += 1
if parser_node.childCount < currentChildIdxSafe {
errors.append( errors.append(
ErrorOnNode(node: parser_node, withError: "Constructor parameters are not yet handled.")) ErrorOnNode(
return node: specific_declaration_node, withError: "Could not find parser for declaration node"
))
} }
if currentChild!.nodeType == "constructorParameters" {
errors.append(
ErrorOnNode(node: currentChild!, withError: "Constructor parameters are not yet handled.")
)
return
// Will increment indexes here.
}
// Skip the '{'
currentChildIdx += 1
currentChildIdxSafe += 1
if parser_node.childCount < currentChildIdxSafe {
errors.append(Error(withMessage: "Missing body of parser declaration"))
return
}
currentChild = parser_node.child(at: currentChildIdx)
if currentChild!.nodeType == "parserLocalElements" {
errors.append(
ErrorOnNode(node: currentChild!, withError: "Parser Local Elements are not yet handled."))
return
// Will increment indexes here.
}
if parser_node.childCount < currentChildIdxSafe {
errors.append(Error(withMessage: "Missing body of parser declaration"))
return
}
if currentChild!.nodeType != "parserStates" {
errors.append(Error(withMessage: "Missing parser states in parser declaration"))
return
}
switch Parser.Compile(
withName: parser_name!, node: currentChild!, withContext: compilation_context)
{
case Result.Ok((let parser, let updated_context)):
// Create a new context with the name of the parser that was just compiled in scope.
compilation_context = compilation_context.update(
newNames: updated_context.names.declare(identifier: parser.name, withValue: parser))
case Result.Error(let error): errors.append(error)
}
// Assume that there is only '}' after -- the parser guaranteed that for us!
} }
if !errors.isEmpty { if !errors.isEmpty {
@@ -207,9 +109,15 @@ public struct Program {
}.joined(separator: ";"))) }.joined(separator: ";")))
} }
// 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! // Any of the types that are in the top-level scope should go into the program!
program.types = Array( program.types = Array(
compilation_context.names.map { (_, v) in compilation_context.types.map { (_, v) in
v v
}) })
return Result.Ok(program) return Result.Ok(program)
+6
View File
@@ -37,3 +37,9 @@ public protocol CompilableType {
type: SwiftTreeSitter.Node, withContext: CompilerContext type: SwiftTreeSitter.Node, withContext: CompilerContext
) -> Result<P4Type?> ) -> Result<P4Type?>
} }
public protocol CompilableDeclaration {
static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(P4Type, CompilerContext)?>
}
+2 -2
View File
@@ -230,7 +230,7 @@ extension VariableDeclarationStatement: CompilableStatement {
identifier: parsed_variablename, withInitializer: initializer), identifier: parsed_variablename, withInitializer: initializer),
// Context with updated names to include the newly declared name. // Context with updated names to include the newly declared name.
context.update( context.update(
newNames: context.names.declare( newInstances: context.instances.declare(
identifier: parsed_variablename, withValue: declaration_p4_type)) identifier: parsed_variablename, withValue: declaration_p4_type))
)) ))
} }
@@ -282,7 +282,7 @@ extension ParserAssignmentStatement: CompilableStatement {
return Result.Error(maybe_parsed_lvalue.error()!) return Result.Error(maybe_parsed_lvalue.error()!)
} }
let check_result = lvalue_identifier.check(to: rvalue, inScopes: context.names) let check_result = lvalue_identifier.check(to: rvalue, inScopes: context.instances)
guard case .Ok(_) = check_result else { guard case .Ok(_) = check_result else {
return Result.Error( return Result.Error(
ErrorOnNode( ErrorOnNode(
+4
View File
@@ -50,10 +50,14 @@ extension P4Struct: CompilableType {
public static func CompileType( public static func CompileType(
type: SwiftTreeSitter.Node, withContext context: CompilerContext type: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(any Common.P4Type)?> { ) -> Common.Result<(any Common.P4Type)?> {
let maybe_parsed_type_id = Identifier.Compile(node: type, withContext: context) let maybe_parsed_type_id = Identifier.Compile(node: type, withContext: context)
guard case .Ok(let parsed_type_id) = maybe_parsed_type_id else { guard case .Ok(let parsed_type_id) = maybe_parsed_type_id else {
return .Error(maybe_parsed_type_id.error()!) return .Error(maybe_parsed_type_id.error()!)
} }
print("Looking up \(parsed_type_id) in \(context.types)")
if case .Ok(let found_type) = context.types.lookup(identifier: parsed_type_id), if case .Ok(let found_type) = context.types.lookup(identifier: parsed_type_id),
let found_struct_type = found_type as? P4Struct let found_struct_type = found_type as? P4Struct
{ {
+20
View File
@@ -0,0 +1,20 @@
// 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 {}
+3 -2
View File
@@ -23,6 +23,7 @@ public struct ExpressionStatement {
public struct Program { public struct Program {
public var types: [P4Type] = Array() public var types: [P4Type] = Array()
public var instances: [P4Type] = Array()
/// Find the program's main parser /// Find the program's main parser
/// ///
@@ -32,8 +33,8 @@ public struct Program {
} }
public func find_parser(withName name: Identifier) -> Result<Parser> { public func find_parser(withName name: Identifier) -> Result<Parser> {
for type in self.types { for instances in self.instances {
guard let parser = type as? Parser else { guard let parser = instances as? Parser else {
continue continue
} }
if parser.name == name { if parser.name == name {
+53
View File
@@ -0,0 +1,53 @@
// 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.ParserRuntime.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(state_result == P4Lang.accept)
}
+1 -17
View File
@@ -61,22 +61,6 @@ import P4Lang
#expect(state.statements.count == 1) #expect(state.statements.count == 1)
} }
@Test func test_simple_compilation_with_instantiation() async throws {
let simple_parser_declaration = """
parser main_parser() {
state start {
true;
false;
transition start;
}
};
bool() main;
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
#expect(#RequireOkResult(program.find_parser(withName: Identifier(name: "main_parser"))))
}
@Test func test_invalid_transition_expression_keyset_expressions() async throws { @Test func test_invalid_transition_expression_keyset_expressions() async throws {
let simple_parser_declaration = """ let simple_parser_declaration = """
parser main_parser() { parser main_parser() {
@@ -114,5 +98,5 @@ import P4Lang
#RequireErrorResult<(EvaluatableStatement, CompilerContext)>( #RequireErrorResult<(EvaluatableStatement, CompilerContext)>(
Error(withMessage: "{2, 154}: Did not find assignment statement"), Error(withMessage: "{2, 154}: Did not find assignment statement"),
ParserAssignmentStatement.Compile( // Note: Calling ParserAssignmentStatement compilation directly. ParserAssignmentStatement.Compile( // Note: Calling ParserAssignmentStatement compilation directly.
node: result.rootNode!, withContext: CompilerContext(withNames: VarTypeScopes())))) node: result.rootNode!, withContext: CompilerContext(withInstances: VarTypeScopes()))))
} }
+5 -1
View File
@@ -63,7 +63,11 @@ export default grammar({
instantiation: $ => seq($.typeRef, '(', optional($.parameterList), ')', $.identifier), instantiation: $ => seq($.typeRef, '(', optional($.parameterList), ')', $.identifier),
// Declarations // Declarations
declaration: $ => seq(choice($.parserDeclaration, $.parserTypeDeclaration)), declaration: $ => seq(choice($.parserDeclaration, $.parserTypeDeclaration, $.type_declaration)),
type_declaration: $=> choice($.struct_declaration),
struct_declaration: $ => seq($.struct, $.identifier, '{', optional($.struct_declaration_fields), '}'),
struct_declaration_fields: $=> repeat1(seq($.variableDeclaration)),
// Make separate productions for the parser type and the parser type declaration because the latter can have type parameters. // Make separate productions for the parser type and the parser type declaration because the latter can have type parameters.
parserTypeDeclaration: $ => seq(optional($.annotations), $.parser, field('parser_name', $.identifier), optional($.typeParameters), '(', optional($.parameterList), ')'), parserTypeDeclaration: $ => seq(optional($.annotations), $.parser, field('parser_name', $.identifier), optional($.typeParameters), '(', optional($.parameterList), ')'),
@@ -232,3 +232,31 @@ parser simple() {
) )
) )
=========================
Simple Struct Type Declaration
=========================
struct Testing {
string fieldA;
};
---
(p4program
(declaration
(type_declaration
(struct_declaration
(struct)
(identifier)
(struct_declaration_fields
(variableDeclaration
(typeRef
(baseType
(string)
)
)
(identifier)
)
)
)
)
)
)