Support Calling Parsers With Parameters

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-04-02 01:28:43 -04:00
parent 7cdbee1999
commit d971aab1fe
12 changed files with 345 additions and 82 deletions
+6
View File
@@ -96,6 +96,12 @@ public enum Result<OKT>: Equatable {
}
}
public func ok() -> Bool {
switch self {
case .Ok(_): true
case .Error(_): false
}
}
public func error() -> Error? {
if case Result.Error(let e) = self {
return e
+52 -27
View File
@@ -171,7 +171,7 @@ extension P4Lang.Parser: CompilableDeclaration {
var parser_name: Common.Identifier? = .none
// Assume that the parameter list is empty!
var parameter_list: ParameterList = ParameterList([])
var parameter_list = ParameterList()
do {
// Parse the parser type (type_node)
@@ -207,23 +207,14 @@ extension P4Lang.Parser: CompilableDeclaration {
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"))
ErrorOnNode(node: type_node!, withError: "Missing parser parameters"))
}
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
@@ -232,6 +223,12 @@ extension P4Lang.Parser: CompilableDeclaration {
return .Error(e)
}
}
// Now, let's put the parameters into scope.
for parameter in parameter_list.parameters {
current_context = current_context.update(
newInstances: current_context.instances.declare(
identifier: parameter.name, withValue: parameter.type))
}
currentChildIdx += 1
@@ -281,53 +278,51 @@ extension P4Lang.Parser: CompilableDeclaration {
}
}
extension ParameterList: Compilable {
public typealias T = ParameterList
public static func Compile(
func parameter_list_compiler(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(ParameterList, CompilerContext)> {
var currentChildIdx = 0
var currentChildIdxSafe = 1
var currentChild: Node? = .none
if node.text == ")" {
// There are no parameters!
return .Ok((ParameterList([]), context))
return Result.Ok((ParameterList([]), context))
}
#RequireNodeType<Node, (ParameterList, CompilerContext)>(
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(
return Result.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) {
switch parameter_list_compiler(node: currentChild!, withContext: context) {
case .Ok(let (ps, _)):
parameters = ps
case .Error(let e): return .Error(e)
case .Error(let e): return Result.Error(e)
}
print("Back here!")
currentChildIdx += 1
currentChildIdxSafe += 1
}
// We may have moved nodes, check/reset currentChild.
if node.childCount < currentChildIdxSafe {
return .Error(
return Result.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))
return Result.Ok((parameters, context))
}
// If this is a comma, we skip it!
@@ -337,7 +332,7 @@ extension ParameterList: Compilable {
}
if node.childCount < currentChildIdxSafe {
return .Error(
return Result.Error(
ErrorOnNode(node: node, withError: "Missing parameter list component"))
}
currentChild = node.child(at: currentChildIdx)
@@ -345,10 +340,40 @@ extension ParameterList: Compilable {
// 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)
return Result.Ok((parameters.addParameter(vds), updated_context))
case .Error(let e): return Result.Error(e)
}
}
extension ParameterList: Compilable {
public typealias T = ParameterList
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(ParameterList, CompilerContext)> {
let parameter_node = node
#RequireNodeType<Node, (ParameterList, CompilerContext)>(
node: parameter_node, type: "parameters", nice_type_name: "Parameters")
var currentChildIdx = 0
var currentChildIdxSafe = 1
// Let's eat the '(' before we start ...
if parameter_node.childCount < currentChildIdxSafe {
return .Error(
ErrorOnNode(node: parameter_node, withError: "Missing '(' in parameter list component"))
}
currentChildIdx += 1
currentChildIdxSafe += 1
if parameter_node.childCount < currentChildIdxSafe {
return .Error(
ErrorOnNode(node: parameter_node, withError: "Missing parameter list component"))
}
let currentChild = parameter_node.child(at: currentChildIdx)
return parameter_list_compiler(node: currentChild!, withContext: context)
}
}
extension Parameter: Compilable {
+4
View File
@@ -38,6 +38,10 @@ public struct Parameter: CustomStringConvertible {
public struct ParameterList: CustomStringConvertible {
public var parameters: [Parameter]
public init() {
self.parameters = Array()
}
public init(_ parameters: [Parameter]) {
self.parameters = parameters
}
+31
View File
@@ -200,3 +200,34 @@ public struct FieldAccessExpression {
self.field = field
}
}
public struct ArgumentList {
public let arguments: [(Int, EvaluatableExpression)]
public init(_ arguments: [EvaluatableExpression]) {
self.arguments = zip(1..., arguments).map { (idx, argument) in
(idx, argument)
}
}
public func compatible(_ parameters: ParameterList) -> Result<()> {
if self.arguments.count != parameters.parameters.count {
return .Error(
Error(
withMessage:
"\(self.arguments.count) arguments found but \(parameters.parameters.count) required"))
}
for (arg, param) in zip(self.arguments, parameters.parameters) {
let arg_index = arg.0
let arg_type = arg.1.type()
if !arg_type.eq(rhs: param.type) {
return .Error(
Error(
withMessage:
"Argument \(arg_index)'s type (\(arg_type)) is incompatible with the parameter type (\(param.type))"
))
}
}
return .Ok(())
}
}
+2 -2
View File
@@ -319,10 +319,11 @@ public struct Parser: P4Type, P4Value {
public var states: ParserStates
public var name: Identifier
public var parameters: ParameterList?
public var parameters: ParameterList
public init(withName name: Identifier) {
self.states = ParserStates()
self.parameters = ParameterList()
self.name = name
}
@@ -349,7 +350,6 @@ public struct Parser: P4Type, P4Value {
}
public var description: String {
let parameters = self.parameters?.description ?? "N/A"
return "Parser \(self.name) with parameters: \(parameters) and states: \(self.states)"
}
+30 -3
View File
@@ -117,8 +117,11 @@ extension ParserStateSelectTransition: EvaluatableParserState {
}
}
extension Parser: ParserExecution {
public func execute(execution: ProgramExecution) -> (InstantiatedParserState, ProgramExecution) {
extension Parser: CallableExecution {
public typealias T = InstantiatedParserState
public func call(
execution: Common.ProgramExecution, arguments: P4Lang.ArgumentList
) -> (P4Lang.InstantiatedParserState, Common.ProgramExecution) {
var execution = execution.enter_scope()
execution = execution.declare(
@@ -146,10 +149,34 @@ extension Parser: ParserExecution {
)
}
// Now that we are assured that there is a start state,
// let's set the arguments.
if case .Error(let e) = arguments.compatible(self.parameters) {
return (
reject, execution.setError(error: Error(withMessage: "Cannot call parser: \(e)"))
)
}
for (parameter, argument) in zip(self.parameters.parameters, arguments.arguments) {
let arg_idx = argument.0
let arg_value = argument.1
let maybe_argument_value = arg_value.evaluate(execution: execution)
guard case .Ok(let argument_value) = maybe_argument_value else {
return (
reject,
execution.setError(
error: Error(withMessage: "Cannot evaluate argument \(arg_idx): \(argument)"))
)
}
execution = execution.declare(identifier: parameter.name, withValue: argument_value)
}
// Evaluate until the state is either accept or reject.
while !current_state.done() && !execution.hasError() {
(current_state, execution) = current_state.execute(program: execution)
}
return (AsInstantiatedParserState(current_state.state()), execution)
return (AsInstantiatedParserState(current_state.state()), execution.exit_scope())
}
}
+3 -2
View File
@@ -34,6 +34,7 @@ public protocol EvaluatableParserState: P4Value {
func state() -> ParserState
}
public protocol ParserExecution {
func execute(execution: ProgramExecution) -> (InstantiatedParserState, ProgramExecution)
public protocol CallableExecution<T> {
associatedtype T
func call(execution: ProgramExecution, arguments: ArgumentList) -> (T, ProgramExecution)
}
+8 -2
View File
@@ -49,8 +49,14 @@ public struct ParserRuntime: CustomStringConvertible {
}
}
/// Run the P4 parser on a given packet
/// Run a P4 parser with no arguments
public func run() -> Result<(ParserState, ProgramExecution)> {
return self.run(withArguments: ArgumentList([]))
}
/// Run the P4 parser on a given packet
public func run(withArguments arguments: ArgumentList) -> Result<(ParserState, ProgramExecution)>
{
let pe =
if let initial = initialValues {
@@ -59,7 +65,7 @@ public struct ParserRuntime: CustomStringConvertible {
ProgramExecution()
}
let (end_state, execution) = parser.execute(execution: pe)
let (end_state, execution) = parser.call(execution: pe, arguments: arguments)
if let error = execution.getError() {
return .Error(error)
}
@@ -160,3 +160,45 @@ import TreeSitterP4
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_select_expression_from_parser_parameters() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (pmtr) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([
P4BooleanValue(withValue: false), P4StringValue(withValue: "Testing"), P4IntValue(withValue: 5),
])
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: args))
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_select_expression_from_parser_parameters2() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (imtr == 5) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([
P4BooleanValue(withValue: false), P4StringValue(withValue: "Testing"), P4IntValue(withValue: 5),
])
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: args))
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@@ -169,3 +169,16 @@ import P4Lang
#expect(parameters.parameters[2].name == Identifier(name: "imtr"))
#expect(parameters.parameters[2].type.eq(rhs: P4Int()))
}
@Test func test_simple_compiler_parser_use_parameters() async throws {
let simple = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
pmtr = true;
transition accept;
}
};
"""
#expect(#RequireOkResult(Program.Compile(simple)))
}
+91
View File
@@ -80,3 +80,94 @@ import TreeSitterP4
Error(withMessage: "Could not find the start state"),
runtime.run()))
}
@Test func test_simple_runtime_parser_with_parameters() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (pmtr) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([
P4BooleanValue(withValue: true), P4StringValue(withValue: "Testing"), P4IntValue(withValue: 5),
])
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: args))
// We should be in the accept state.
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_simple_runtime_parser_with_mismatched_parameter_types() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (pmtr) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([
P4BooleanValue(withValue: true), P4BooleanValue(withValue: false), P4IntValue(withValue: 5),
])
#expect(
#RequireErrorResult<(ParserState, ProgramExecution)>(
Error(withMessage: "Cannot call parser: Argument 2's type (Boolean) is incompatible with the parameter type (String)"),
runtime.run(withArguments: args)))
}
@Test func test_simple_runtime_parser_with_mismatched_parameter_types2() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (pmtr) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([
P4IntValue(withValue: 5), P4StringValue(withValue: "Testing"), P4IntValue(withValue: 5),
])
#expect(
#RequireErrorResult<(ParserState, ProgramExecution)>(
Error(withMessage: "Cannot call parser: Argument 1's type (Int) is incompatible with the parameter type (Boolean)"),
runtime.run(withArguments: args)))
}
@Test func test_simple_runtime_parser_with_mismatched_parameter_counts() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
transition select (pmtr) {
true: accept;
false: reject;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([P4BooleanValue(withValue: true)])
#expect(
#RequireErrorResult<(ParserState, ProgramExecution)>(
Error(withMessage: "Cannot call parser: 1 arguments found but 3 required"),
runtime.run(withArguments: args)))
}
@@ -262,3 +262,20 @@ import TreeSitterP4
),
Program.Compile(simple_parser_declaration, withGlobalInstances: test_types)))
}
@Test func test_simple_compiler_parser_parameters_invalid_types() async throws {
let simple_parser_declaration = """
parser main_parser(bool pmtr, string smtr, int imtr) {
state start {
pmtr = 1;
transition accept;
}
};
"""
#expect(
#RequireErrorResult(
Error(
withMessage: "{85, 9}: Failed to parse a statement element: {85, 4}: Cannot assign value with type Int to identifier pmtr with type Boolean"
),
Program.Compile(simple_parser_declaration)))
}