Start Handling Control Blocks

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-04-06 08:47:44 -04:00
parent 443b21b890
commit 29dfa62472
7 changed files with 839 additions and 2 deletions
+12 -1
View File
@@ -9,9 +9,20 @@ Very, very alpha:
1. Limited parts of the language can be parsed. 1. Limited parts of the language can be parsed.
2. Limited programs can be evaluated. 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 ```P4
control simple(bool x, bool y) {
action a(int z) {
z = false;
}
table t {
key = {
x: exact;
y: exact;
}
}
};
struct Testing { struct Testing {
bool yesno; bool yesno;
int count; int count;
+4
View File
@@ -39,6 +39,10 @@ public class Identifier: CustomStringConvertible, Comparable, Hashable {
return lhs.name == rhs.name 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 { public static func < (lhs: Identifier, rhs: Identifier) -> Bool {
return lhs.name < rhs.name return lhs.name < rhs.name
} }
+402
View File
@@ -29,6 +29,7 @@ extension Declaration: CompilableDeclaration {
let declaration_compilers: [String: CompilableDeclaration.Type] = [ let declaration_compilers: [String: CompilableDeclaration.Type] = [
"function_declaration": FunctionDeclaration.self, "function_declaration": FunctionDeclaration.self,
"control_declaration": Control.self,
"type_declaration": StructDeclaration.self, // Assume that type declarations are struct declarations. "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, (P4Type, CompilerContext)?>(
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, (P4Type, CompilerContext)>(
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, (P4Type, CompilerContext)>(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, (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(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, (TableKeyMatchType, CompilerContext)>(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, (P4Type, CompilerContext)>(
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, (P4Type, CompilerContext)>(
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))
}
}
+211
View File
@@ -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 <https://www.gnu.org/licenses/>.
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: []))
}
}
+141
View File
@@ -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 <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;
}
}
};
"""
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))
)
}
+16 -1
View File
@@ -64,7 +64,7 @@ export default grammar({
instantiation: $ => seq($.typeRef, '(', optional($.parameter_list), ')', $.identifier), instantiation: $ => seq($.typeRef, '(', optional($.parameter_list), ')', $.identifier),
// Declarations // 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), type_declaration: $=> choice($.struct_declaration),
struct_declaration: $ => seq($.struct, $.identifier, '{', optional($.struct_declaration_fields), '}'), 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), 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 // Statements
// General statements // General statements
@@ -150,6 +164,7 @@ export default grammar({
enum: $ => "enum", enum: $ => "enum",
error: $ => "error", error: $ => "error",
exit: $ => "exit", exit: $ => "exit",
exact: $ => "exact",
extern: $ => "extern", extern: $ => "extern",
false: $ => "false", false: $ => "false",
header: $ => "header", header: $ => "header",
+53
View File
@@ -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)
)
)
)
)
)
)
)
)