diff --git a/README.md b/README.md index 4d0c9cc..4af8347 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,20 @@ Very, very alpha: 1. Limited parts of the language can be parsed. 2. Limited programs can be evaluated. -As an example of what can be parsed and evaluated, here is a fairly complex P4 program from our unit tests: +As an example of what can be parsed and evaluated, here is a fairly complex P4 program cobbled together from our unit tests: ```P4 +control simple(bool x, bool y) { + action a(int z) { + z = false; + } + table t { + key = { + x: exact; + y: exact; + } + } +}; struct Testing { bool yesno; int count; diff --git a/Sources/Common/ProgramTypes.swift b/Sources/Common/ProgramTypes.swift index c691ea0..8beaa3e 100644 --- a/Sources/Common/ProgramTypes.swift +++ b/Sources/Common/ProgramTypes.swift @@ -39,6 +39,10 @@ public class Identifier: CustomStringConvertible, Comparable, Hashable { return lhs.name == rhs.name } + public static func == (lhs: Identifier, rhs: String) -> Bool { + return Identifier(name: rhs) == lhs + } + public static func < (lhs: Identifier, rhs: Identifier) -> Bool { return lhs.name < rhs.name } diff --git a/Sources/P4Compiler/Declarations.swift b/Sources/P4Compiler/Declarations.swift index 75e5718..7a5e5b0 100644 --- a/Sources/P4Compiler/Declarations.swift +++ b/Sources/P4Compiler/Declarations.swift @@ -29,6 +29,7 @@ extension Declaration: CompilableDeclaration { let declaration_compilers: [String: CompilableDeclaration.Type] = [ "function_declaration": FunctionDeclaration.self, + "control_declaration": Control.self, "type_declaration": StructDeclaration.self, // Assume that type declarations are struct declarations. ] @@ -556,3 +557,404 @@ extension Parameter: Compilable { )) } } + +extension Control: CompilableDeclaration { + public static func Compile( + node: SwiftTreeSitter.Node, withContext context: CompilerContext + ) -> Common.Result<(any Common.P4Type, CompilerContext)?> { + + #SkipUnlessNodeType( + node: node, type: "control_declaration") + + var currentChildIdx = 0 + var currentChildIdxSafe = 1 + var currentChild: Node? = .none + + var local_context = context + + // Skip control keyword + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing control declaration component")) + } + + currentChildIdx += 1 + currentChildIdxSafe += 1 + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing control declaration component")) + } + currentChild = node.child(at: currentChildIdx) + + guard + case .Ok(let control_name) = Identifier.Compile( + node: currentChild!, withContext: local_context) + else { + return Result.Error( + Error(withMessage: "Could not parse a parameter name from \(currentChild!.text!)")) + } + + currentChildIdx += 1 + currentChildIdxSafe += 1 + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing control declaration component")) + } + currentChild = node.child(at: currentChildIdx) + + let maybe_control_parameters = ParameterList.Compile( + node: currentChild!, 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) + + // Skip the '{' + currentChildIdx += 2 + currentChildIdxSafe += 2 + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing control declaration component")) + } + + var actions: [Action] = Array() + var tables: [Table] = Array() + + // Because the final child + // is the '}'. + // \/\/ + for currentChildIdx in currentChildIdx..<(node.childCount - 1) { + let currentChild = node.child(at: currentChildIdx)! + if currentChild.nodeType == "action_declaration" { + let maybe_action_declaration = Action.Compile( + node: currentChild, 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 + } else if currentChild.nodeType == "table_declaration" { + let maybe_table_declaration = Table.Compile( + node: currentChild, 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 { + return .Error( + ErrorOnNode(node: currentChild, withError: "Uknown node type in control declaration")) + } + } + + // 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(ErrorOnNode(node: node, withError: "More than one table in control declaration")) + } + + let declared_control = + (Control( + named: control_name, withParameters: control_parameters, withTable: tables[0], + withActions: Actions(withActions: actions)) + as P4Type) + + // 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.update( + newInstances: context.instances.declare( + identifier: control_name, withValue: declared_control)) + )) + } +} + +extension Action: Compilable { + public typealias T = Action + public static func Compile( + node: SwiftTreeSitter.Node, withContext context: CompilerContext + ) -> Common.Result<(P4Lang.Action, CompilerContext)> { + #RequireNodeType( + node: node, type: "action_declaration", nice_type_name: "Action Declaration") + + var currentChildIdx = 1 + var currentChildIdxSafe = 2 + var currentChild: Node? = .none + var current_context = context + + // Skip action keyword + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing action declaration component")) + } + currentChild = node.child(at: currentChildIdx) + + guard + case .Ok(let action_name) = Identifier.Compile( + node: currentChild!, withContext: current_context) + else { + return Result.Error( + Error(withMessage: "Could not parse an action name from \(currentChild!.text!)")) + } + + currentChildIdx += 1 + currentChildIdxSafe += 1 + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing action declaration component")) + } + currentChild = node.child(at: currentChildIdx) + + let maybe_action_parameters = ParameterList.Compile( + node: currentChild!, 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 + + currentChildIdx += 1 + currentChildIdxSafe += 1 + if node.childCount < currentChildIdxSafe { + return Result.Error( + ErrorOnNode( + node: node, withError: "Missing action declaration component")) + } + currentChild = node.child(at: currentChildIdx) + + // 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 = Parser.Statement.Compile( + node: currentChild!, withContext: context.update(newInstances: function_scope)) + guard case .Ok((let action_body, _)) = maybe_action_body else { + return .Error(maybe_action_body.error()!) + } + + return .Ok( + ( + Action(named: action_name, withParameters: action_parameters, withBody: action_body), + 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: node, type: "table_key_entry", nice_type_name: "Table Key Entry") + + var currentChildIdx = 0 + var currentChildIdxSafe = 1 + var currentChild: Node? = .none + + let current_context = context + + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing table key entry declaration component")) + } + currentChild = node.child(at: currentChildIdx) + + let maybe_keyset_expression = KeysetExpression.compile(node: currentChild!, withContext: current_context) + guard case .Ok(let keyset_expression) = maybe_keyset_expression else { + return Result.Error(maybe_keyset_expression.error()!) + } + + // Skip the ':' + currentChildIdx += 2 + currentChildIdxSafe += 2 + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing table key entry declaration component")) + } + currentChild = node.child(at: currentChildIdx) + + let maybe_match_type = TableKeyMatchType.Compile(node: currentChild!, 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: node, type: "table_key_match_type", nice_type_name: "Table Key Match Type") + + if node.text! == "exact" { + return .Ok((TableKeyMatchType.Exact, context)) + } + return .Error(ErrorOnNode(node: node, 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: node, type: "table_keys", nice_type_name: "Table Keys") + + // Skip the + // keys = { + // 0 1 2 + let currentChildIdx = 3 + let currentChildIdxSafe = 4 + var currentChild: Node? = .none + var current_context = context + + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing table keys declaration component in control declaration")) + } + currentChild = node.child(at: currentChildIdx) + + var entries: [TableKeyEntry] = Array() + var errors: [Error] = Array() + + currentChild!.enumerateNamedChildren() { entry in + switch TableKeyEntry.Compile(node: currentChild!, withContext: current_context) { + case .Ok((let keyset_expression, let updated_context)): + entries.append(keyset_expression) + current_context = updated_context + case .Error(let e): errors.append(e) + } + } + + if !errors.isEmpty { + return .Error( + Error( + withMessage: "Error(s) parsing table key: " + + (errors.map { error in + return "\(error.msg)" + }.joined(separator: ";")))) + } + + return .Ok((TableKeys(withEntries: entries), current_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: node, type: "table_property_list", nice_type_name: "Table Property List") + + var current_context = context + + var keys: [TableKeys] = Array() + var _: [Action] = Array() // Actions are not yet supported + var errors: [Error] = 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" { + errors.append( + ErrorOnNode( + node: child, withError: "Actions in table property lists are not yet supported")) + } else { + errors.append( + ErrorOnNode(node: child, withError: "Uknown node type in control declaration")) + } + } + + if !errors.isEmpty { + return .Error( + Error( + withMessage: "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( + ErrorOnNode(node: node, withError: "More than one key set in table property list")) + } + + return .Ok((TablePropertyList(withActions: TableActions(), 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: table_declaration_node, type: "table_declaration", nice_type_name: "Table Declaration") + + var currentChildIdx = 1 + var currentChildIdxSafe = 2 + var currentChild: Node? = .none + + let current_context = context + + if table_declaration_node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: table_declaration_node, withError: "Missing table declaration component")) + } + currentChild = table_declaration_node.child(at: currentChildIdx) + + guard + case .Ok(let table_name) = Identifier.Compile( + node: currentChild!, withContext: current_context) + else { + return Result.Error( + Error(withMessage: "Could not parse a table name from \(currentChild!.text!)")) + } + + // Skip the '{' + currentChildIdx += 2 + currentChildIdxSafe += 2 + if table_declaration_node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: table_declaration_node, withError: "Missing table declaration component")) + } + currentChild = table_declaration_node.child(at: currentChildIdx) + + let maybe_table_property_list = TablePropertyList.Compile( + node: currentChild!, 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)) + } +} \ No newline at end of file diff --git a/Sources/P4Lang/Control.swift b/Sources/P4Lang/Control.swift new file mode 100644 index 0000000..43cc97d --- /dev/null +++ b/Sources/P4Lang/Control.swift @@ -0,0 +1,211 @@ +// 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 . + +import Common + +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: EvaluatableStatement? + public var params: ParameterList + public var name: Identifier + + public init( + named name: Identifier, withParameters parameters: ParameterList, + withBody body: EvaluatableStatement? + ) { + self.name = name + self.params = parameters + self.body = body + } + +} + +public struct Actions: CustomStringConvertible { + 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 { + let key: KeysetExpression + 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 { + let entries: [TableKeyEntry] + + public init(withEntries entries: [TableKeyEntry]) { + self.entries = entries + } + public init() { + self.entries = [] + } + + public var description: String { + return "Table Keys: " + self.entries.map() { entry in + return "\(entry)" + }.joined(separator: ";") + } +} + +/// TODO +public struct TableActions { + public init() {} +} + +public struct TablePropertyList: CustomStringConvertible { + let actions: TableActions + let keys: TableKeys + public init(withActions actions: TableActions, 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 { + let properties: TablePropertyList + let name: Identifier + + public init(withName name: Identifier, withPropertyList property_list: TablePropertyList) { + self.name = name + self.properties = property_list + } + + public var description: String { + return "Table named: \(self.name) \(self.properties)" + } +} + +public struct Control: P4Type, P4Value, Equatable, CustomStringConvertible { + public static func == (lhs: Control, rhs: Control) -> Bool { + // Two "bare" controls are always equal. + return true + } + + public func eq(rhs: any Common.P4Type) -> Bool { + return switch rhs { + case is Control: 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.P4Value) -> Bool { + return switch rhs { + case is Control: true + default: false + } + } + + public func lt(rhs: any Common.P4Value) -> Bool { + return switch rhs { + case is Control: true + default: false + } + } + + public func lte(rhs: any Common.P4Value) -> Bool { + return switch rhs { + case is Control: true + default: false + } + } + + public func gt(rhs: any Common.P4Value) -> Bool { + return switch rhs { + case is Control: true + default: false + } + } + + public func gte(rhs: any Common.P4Value) -> 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)" + } + + let actions: Actions + let table: Table + let _parameters: ParameterList + let _name: Identifier + + public var parameters: ParameterList { + get { + _parameters + } + } + + public var name: Identifier { + get { + _name + } + } + + public init(named: Identifier, withParameters parameters: ParameterList, withTable table: Table, withActions actions: Actions) { + self._name = named + self._parameters = parameters + self.actions = actions + self.table = table + } + + public func def() -> any P4Value { + return Control( + named: Identifier(name: ""), + withParameters: ParameterList(), + withTable: Table( + withName: Identifier(name: "empty"), + withPropertyList: TablePropertyList(withActions: TableActions(), withKeys: TableKeys())), + withActions: Actions(withActions: [])) + } + +} diff --git a/Tests/p4rseTests/ControlCompilerTests.swift b/Tests/p4rseTests/ControlCompilerTests.swift new file mode 100644 index 0000000..e4a8333 --- /dev/null +++ b/Tests/p4rseTests/ControlCompilerTests.swift @@ -0,0 +1,141 @@ +// 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 . + +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; + } + } + }; + """ + let x = { (tipe: P4Type) -> Bool in + switch tipe { + 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; + } + } + }; + control complex() { + action b() { + } + table t { + key = { + true: exact; + } + } + }; + """ + + let filter = { (tipe: P4Type) -> Bool in + switch tipe { + 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_parameters() async throws { + let simple_parser_declaration = """ + control simple(bool x, bool y) { + action a() { + } + table t { + key = { + x: exact; + y: exact; + } + } + }; + """ + #expect(#RequireOkResult(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; + } + } + }; + """ + #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; + } + } + }; + """ + #expect( + #RequireErrorResult( + Error( + withMessage: + "{51, 20}: Failed to parse a statement element: {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)) + ) +} \ No newline at end of file diff --git a/tree-sitter-p4/grammar.js b/tree-sitter-p4/grammar.js index 8192a1f..83e25ff 100644 --- a/tree-sitter-p4/grammar.js +++ b/tree-sitter-p4/grammar.js @@ -64,7 +64,7 @@ export default grammar({ instantiation: $ => seq($.typeRef, '(', optional($.parameter_list), ')', $.identifier), // Declarations - declaration: $ => seq(choice($.parserDeclaration, $.parserTypeDeclaration, $.type_declaration, $.function_declaration)), + declaration: $ => seq(choice($.parserDeclaration, $.parserTypeDeclaration, $.type_declaration, $.function_declaration, $.control_declaration)), type_declaration: $=> choice($.struct_declaration), struct_declaration: $ => seq($.struct, $.identifier, '{', optional($.struct_declaration_fields), '}'), @@ -78,6 +78,20 @@ export default grammar({ variableDeclaration: $ => seq(optional($.annotations), $.typeRef, field('variable_name', $.identifier), optional(seq($.assignment, $.expression)), $._semicolon), + // Control declarations + control_declaration: $ => seq($.control, $.identifier, $.parameters, '{', repeat(choice($.table_declaration, $.action_declaration)), '}'), + action_declaration: $ => seq($.action, $.identifier, $.parameters, $.statement), + table_declaration: $ => seq($.table, $.identifier, '{', optional($.table_property_list), '}'), + + // Table property list + table_property_list: $ => repeat1(choice($.table_keys, $.table_actions)), + table_keys: $=> seq($.key, '=', '{', repeat($.table_key_entry), '}'), + table_key_entry: $=> seq($.keysetExpression, ':', $.table_key_match_type, $._semicolon), + table_actions: $=> seq($.actions, '=', '{', optional(repeat1($.identifier)), '}'), + + // match types + table_key_match_type: $ => choice($.exact), // support exact only for now. + // Statements // General statements @@ -150,6 +164,7 @@ export default grammar({ enum: $ => "enum", error: $ => "error", exit: $ => "exit", + exact: $ => "exact", extern: $ => "extern", false: $ => "false", header: $ => "header", diff --git a/tree-sitter-p4/test/corpus/control.txt b/tree-sitter-p4/test/corpus/control.txt new file mode 100644 index 0000000..af731b2 --- /dev/null +++ b/tree-sitter-p4/test/corpus/control.txt @@ -0,0 +1,53 @@ +========================= +Simple Control Declaration +========================= +control simple() { + action a() { + } + table t { + key = { + x: exact; + } + } +}; + +--- +(p4program + (declaration + (control_declaration + (control) + (identifier) + (parameters) + (action_declaration + (action) + (identifier) + (parameters) + (statement + (blockStatement) + ) + ) + (table_declaration + (table) + (identifier) + (table_property_list + (table_keys + (key) + (table_key_entry + (keysetExpression + (expression + (simple_expression + (identifier) + ) + ) + ) + (table_key_match_type + (exact) + ) + ) + ) + ) + ) + ) + ) +) +