From 8962235acaedf853a500a13bef3584dcb5835cb2 Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Mon, 30 Mar 2026 07:57:41 -0400 Subject: [PATCH] Support Parameters For Parsers Signed-off-by: Will Hawkins --- Sources/P4Compiler/Declarations.swift | 204 +++++++++++++++++++-- Sources/P4Compiler/Parser.swift | 4 +- Sources/P4Compiler/Protocols.swift | 7 + Sources/P4Lang/Declarations.swift | 35 ++++ Sources/P4Lang/Parser.swift | 10 +- Tests/p4rseTests/ParserCompilerTests.swift | 69 +++++++ tree-sitter-p4/grammar.js | 12 +- tree-sitter-p4/test/corpus/parsers.txt | 2 +- 8 files changed, 317 insertions(+), 26 deletions(-) diff --git a/Sources/P4Compiler/Declarations.swift b/Sources/P4Compiler/Declarations.swift index 6447e4a..33b1821 100644 --- a/Sources/P4Compiler/Declarations.swift +++ b/Sources/P4Compiler/Declarations.swift @@ -146,11 +146,11 @@ 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) - } + #SkipUnlessNodeType( + node: parser_node, type: "parserDeclaration") + + var current_context = context var currentChildIdx = 0 var currentChildIdxSafe = 1 @@ -170,6 +170,9 @@ extension P4Lang.Parser: CompilableDeclaration { let type_node = currentChild var parser_name: Common.Identifier? = .none + // Assume that the parameter list is empty! + var parameter_list: ParameterList = ParameterList([]) + do { // Parse the parser type (type_node) var currentChildIdx = 0 @@ -198,25 +201,44 @@ extension P4Lang.Parser: CompilableDeclaration { } currentChild = type_node?.child(at: currentChildIdx) - switch Identifier.Compile(node: currentChild!, withContext: context) { + switch Identifier.Compile(node: currentChild!, withContext: current_context) { case .Ok(let id): parser_name = id case .Error(let e): return .Error(e) } + + // Now, see if there are any parameters + currentChildIdx += 1 + currentChildIdxSafe += 1 + if currentChildIdxSafe < type_node!.childCount { + + // There is something that _should_ be parameters! + + // skip the '(' + currentChildIdx += 1 + currentChildIdxSafe += 1 + if type_node!.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: currentChild!, withError: "Missing ( before parameter list")) + } + + currentChild = type_node?.child(at: currentChildIdx) + + switch ParameterList.Compile(node: currentChild!, withContext: current_context) { + case .Ok(let (parsed_parameter_list, updated_context)): + parameter_list = parsed_parameter_list + current_context = updated_context + case .Error(let e): + return .Error(e) + } + } } 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. + ErrorOnNode(node: parser_node, withError: "Missing parser declaration component")) } // Skip the '{' @@ -242,7 +264,8 @@ extension P4Lang.Parser: CompilableDeclaration { } switch Parser.Compile( - withName: parser_name!, node: currentChild!, withContext: context) + withName: parser_name!, withParameters: parameter_list, node: currentChild!, + withContext: current_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. @@ -255,7 +278,156 @@ extension P4Lang.Parser: CompilableDeclaration { )) case Result.Error(let error): return .Error(error) } - - // Assume that there is only '}' after -- the parser guaranteed that for us! + } +} + +extension ParameterList: Compilable { + public typealias T = ParameterList + public static func Compile( + node: SwiftTreeSitter.Node, withContext context: CompilerContext + ) -> Common.Result<(ParameterList, CompilerContext)> { + + if node.text == ")" { + // There are no parameters! + return .Ok((ParameterList([]), context)) + } + + #RequireNodeType( + node: node, type: "parameter_list", nice_type_name: "Parameter List") + + var currentChildIdx = 0 + var currentChildIdxSafe = 1 + var currentChild: Node? = .none + var parameters: ParameterList = ParameterList([]) + + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing parameter list component")) + } + + currentChild = node.child(at: currentChildIdx) + if currentChild?.nodeType == "parameter_list" { + switch ParameterList.Compile(node: currentChild!, withContext: context) { + case .Ok(let (ps, _)): + parameters = ps + case .Error(let e): return .Error(e) + } + + print("Back here!") + currentChildIdx += 1 + currentChildIdxSafe += 1 + } + + // We may have moved nodes, check/reset currentChild. + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing parameter list component")) + } + currentChild = node.child(at: currentChildIdx) + + // If this is a ')', we are done. + if currentChild?.text == ")" { + return .Ok((parameters, context)) + } + + // If this is a comma, we skip it! + if currentChild?.text == "," { + currentChildIdx += 1 + currentChildIdxSafe += 1 + } + + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing parameter list component")) + } + currentChild = node.child(at: currentChildIdx) + + // Otherwise, there should be one parameter left! + switch Parameter.Compile(node: currentChild!, withContext: context) { + case .Ok(let (vds, updated_context)): + return .Ok((parameters.addParameter(vds), updated_context)) + case .Error(let e): return .Error(e) + } + } +} + +extension Parameter: Compilable { + public typealias T = Parameter + public static func Compile( + node: Node, withContext context: CompilerContext + ) -> Result<(Parameter, CompilerContext)> { + + #RequireNodeType( + node: node, type: "parameter", nice_type_name: "parameter") + + var currentChildIdx = 0 + var currentChildIdxSafe = 1 + var currentChild: Node? = .none + + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing parameter declaration component")) + } + + currentChild = node.child(at: currentChildIdx) + + // Annotation? + if currentChild!.nodeType == "annotations" { + return .Error( + ErrorOnNode( + node: currentChild!, + withError: "Annotations in parameter declarations are not yet handled")) + // Will increment indexes here. + } + + // Direction? + if currentChild!.nodeType == "direction" { + return .Error( + ErrorOnNode( + node: currentChild!, withError: "Direction in parameter declarations are not yet handled" + )) + // Will increment indexes here. + } + + if currentChild!.nodeType != "typeRef" { + return Result.Error( + ErrorOnNode( + node: node, withError: "Did not find type name for parameter declaration")) + } + + guard + case .Ok(let parameter_type) = Types.CompileType(type: currentChild!, withContext: context) + else { + return Result.Error( + Error(withMessage: "Could not parse a P4 type from \(currentChild!.text!)")) + } + + currentChildIdx += 1 + currentChildIdxSafe += 1 + if node.childCount < currentChildIdxSafe { + return .Error( + ErrorOnNode(node: node, withError: "Missing parameter declaration component")) + } + + currentChild = node.child(at: currentChildIdx) + if currentChild!.nodeType != "identifier" { + return Result.Error( + ErrorOnNode( + node: node, withError: "Did not find identifier for parameter statement")) + } + + guard + case .Ok(let parameter_name) = Identifier.Compile(node: currentChild!, withContext: context) + else { + return Result.Error( + Error(withMessage: "Could not parse a parameter name from \(currentChild!.text!)")) + } + + return Result.Ok( + ( + Parameter( + identifier: parameter_name, withType: parameter_type), + context + )) } } diff --git a/Sources/P4Compiler/Parser.swift b/Sources/P4Compiler/Parser.swift index e79267f..1969ab8 100644 --- a/Sources/P4Compiler/Parser.swift +++ b/Sources/P4Compiler/Parser.swift @@ -266,11 +266,11 @@ public struct Parser { } static func Compile( - withName name: Common.Identifier, node: Node, + withName name: Common.Identifier, withParameters parameters: ParameterList, node: Node, withContext context: CompilerContext ) -> Result<(P4Lang.Parser, CompilerContext)> { - var parser = P4Lang.Parser(withName: name) + var parser = P4Lang.Parser(withName: name, withParameters: parameters) // Build a state from each one listed. var error: Error? = .none diff --git a/Sources/P4Compiler/Protocols.swift b/Sources/P4Compiler/Protocols.swift index 60d725f..f9ff25f 100644 --- a/Sources/P4Compiler/Protocols.swift +++ b/Sources/P4Compiler/Protocols.swift @@ -43,3 +43,10 @@ public protocol CompilableDeclaration { node: Node, withContext context: CompilerContext ) -> Result<(P4Type, CompilerContext)?> } + +public protocol Compilable { + associatedtype T + static func Compile( + node: Node, withContext context: CompilerContext + ) -> Result<(T, CompilerContext)> +} diff --git a/Sources/P4Lang/Declarations.swift b/Sources/P4Lang/Declarations.swift index 4562d5f..013db12 100644 --- a/Sources/P4Lang/Declarations.swift +++ b/Sources/P4Lang/Declarations.swift @@ -18,3 +18,38 @@ import Common public struct Declaration {} + +public struct Parameter: CustomStringConvertible { + public var name: Identifier + public var type: P4Type + + public init( + identifier: Identifier, withType type: P4Type + ) { + self.name = identifier + self.type = type + } + + public var description: String { + return "Parameter: \(self.name) with type \(self.type)" + } +} + +public struct ParameterList: CustomStringConvertible { + public var parameters: [Parameter] + + public init(_ parameters: [Parameter]) { + self.parameters = parameters + } + + public func addParameter(_ parameter: Parameter) -> ParameterList { + return ParameterList(self.parameters + [parameter]) + } + + public var description: String { + let parameters = self.parameters.map { parameter in + parameter.description + }.joined(separator: ";") + return "Parameter list: \(parameters)" + } +} diff --git a/Sources/P4Lang/Parser.swift b/Sources/P4Lang/Parser.swift index 8d38b8c..4de2987 100644 --- a/Sources/P4Lang/Parser.swift +++ b/Sources/P4Lang/Parser.swift @@ -319,12 +319,19 @@ public struct Parser: P4Type, P4Value { public var states: ParserStates public var name: Identifier + public var parameters: ParameterList? public init(withName name: Identifier) { self.states = ParserStates() self.name = name } + public init(withName name: Identifier, withParameters parameters: ParameterList) { + self.states = ParserStates() + self.parameters = parameters + self.name = name + } + public func findStartState() -> ParserState? { for state in states.states { if state.state == Identifier(name: "start") { @@ -342,7 +349,8 @@ public struct Parser: P4Type, P4Value { } public var description: String { - return "Parser" + let parameters = self.parameters?.description ?? "N/A" + return "Parser \(self.name) with parameters: \(parameters) and states: \(self.states)" } public func def() -> any P4Value { diff --git a/Tests/p4rseTests/ParserCompilerTests.swift b/Tests/p4rseTests/ParserCompilerTests.swift index a80bacd..6300ce2 100644 --- a/Tests/p4rseTests/ParserCompilerTests.swift +++ b/Tests/p4rseTests/ParserCompilerTests.swift @@ -100,3 +100,72 @@ import P4Lang ParserAssignmentStatement.Compile( // Note: Calling ParserAssignmentStatement compilation directly. node: result.rootNode!, withContext: CompilerContext(withInstances: VarTypeScopes())))) } + +@Test func test_simple_compiler_parser_with_parameters() async throws { + let simple = """ + parser main_parser(bool pmtr) { + state start { + transition accept; + } + }; + """ + + let program = try! #UseOkResult(Program.Compile(simple)) + let parser = try! #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + let parameters = try! #require(parser.parameters) + + // Check that the parameters match. + #expect(parameters.parameters.count == 1) + + #expect(parameters.parameters[0].name == Identifier(name: "pmtr")) + #expect(parameters.parameters[0].type.eq(rhs: P4Boolean())) +} + +@Test func test_simple_compiler_parser_with_multiple_parameters() async throws { + let simple = """ + parser main_parser(bool pmtr, string smtr) { + state start { + transition accept; + } + }; + """ + + let program = try! #UseOkResult(Program.Compile(simple)) + let parser = try! #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + let parameters = try! #require(parser.parameters) + + // Check that the parameters match. + #expect(parameters.parameters.count == 2) + + #expect(parameters.parameters[0].name == Identifier(name: "pmtr")) + #expect(parameters.parameters[0].type.eq(rhs: P4Boolean())) + + #expect(parameters.parameters[1].name == Identifier(name: "smtr")) + #expect(parameters.parameters[1].type.eq(rhs: P4String())) +} + +@Test func test_simple_compiler_parser_with_multiple_parameters2() async throws { + let simple = """ + parser main_parser(bool pmtr, string smtr, int imtr) { + state start { + transition accept; + } + }; + """ + + let program = try! #UseOkResult(Program.Compile(simple)) + let parser = try! #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser"))) + let parameters = try! #require(parser.parameters) + + // Check that the parameters match. + #expect(parameters.parameters.count == 3) + + #expect(parameters.parameters[0].name == Identifier(name: "pmtr")) + #expect(parameters.parameters[0].type.eq(rhs: P4Boolean())) + + #expect(parameters.parameters[1].name == Identifier(name: "smtr")) + #expect(parameters.parameters[1].type.eq(rhs: P4String())) + + #expect(parameters.parameters[2].name == Identifier(name: "imtr")) + #expect(parameters.parameters[2].type.eq(rhs: P4Int())) +} diff --git a/tree-sitter-p4/grammar.js b/tree-sitter-p4/grammar.js index 0a061e9..e64e00b 100644 --- a/tree-sitter-p4/grammar.js +++ b/tree-sitter-p4/grammar.js @@ -29,17 +29,17 @@ export default grammar({ // Common - Parameters typeParameters: $ => seq('<', $.typeParameterList, '>'), typeParameterList: $ => choice("[a-z]+", seq($.typeParameterList, ',', "[a-z]+")), - parameterList: $ => choice($.parameter, seq($.parameterList, ',', $.parameter)), + parameter_list: $ => choice($.parameter, seq($.parameter_list, ',', $.parameter)), parameter: $ => choice(seq(optional($.annotations), optional($.direction), $.typeRef, $.identifier), seq(optional($.annotations), optional($.direction), $.typeRef, $.identifier, '=', $.expression)), direction: $ => choice($.in, $.out, $.inout), // Common - Types typeRef: $ => choice($.baseType, $.type_identifier), baseType: $ => choice($.bool, $.error, $.string, $.int, $.bit /* omitting "templated" types" */), - constructorParameters: $ => seq('(', optional($.parameterList), ')'), + constructor_parameters: $ => seq('(', optional($.parameter_list), ')'), // Common - Parsers - parserType: $ => seq(optional($.annotations), $.parser, field('parser_name', $.identifier), optional($.typeParameters), '(', optional($.parameterList), ')'), + parserType: $ => seq(optional($.annotations), $.parser, field('parser_name', $.identifier), optional($.typeParameters), '(', optional($.parameter_list), ')'), // Mark with higher precedence so that the local states are preferred when parsing! // TODO: Test! @@ -60,7 +60,7 @@ export default grammar({ // Instantiation - instantiation: $ => seq($.typeRef, '(', optional($.parameterList), ')', $.identifier), + instantiation: $ => seq($.typeRef, '(', optional($.parameter_list), ')', $.identifier), // Declarations declaration: $ => seq(choice($.parserDeclaration, $.parserTypeDeclaration, $.type_declaration)), @@ -70,8 +70,8 @@ export default grammar({ 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. - parserTypeDeclaration: $ => seq(optional($.annotations), $.parser, field('parser_name', $.identifier), optional($.typeParameters), '(', optional($.parameterList), ')'), - parserDeclaration: $ => seq($.parserType, optional($.constructorParameters), '{', optional($.parserLocalElements), $.parserStates, '}'), + parserTypeDeclaration: $ => seq(optional($.annotations), $.parser, field('parser_name', $.identifier), optional($.typeParameters), '(', optional($.parameter_list), ')'), + parserDeclaration: $ => seq($.parserType, '{', optional($.parserLocalElements), $.parserStates, '}'), variableDeclaration: $ => seq(optional($.annotations), $.typeRef, field('variable_name', $.identifier), optional(seq($.assignment, $.expression)), $._semicolon), diff --git a/tree-sitter-p4/test/corpus/parsers.txt b/tree-sitter-p4/test/corpus/parsers.txt index 70cad21..5a75eca 100644 --- a/tree-sitter-p4/test/corpus/parsers.txt +++ b/tree-sitter-p4/test/corpus/parsers.txt @@ -48,7 +48,7 @@ parser imple(bool pname) { (parserType (parser) (identifier) - (parameterList + (parameter_list (parameter (typeRef (baseType