Support Function Calls

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-04-09 23:16:27 -04:00
parent 708f65a0a9
commit d39127ac17
21 changed files with 984 additions and 311 deletions
+24 -8
View File
@@ -21,6 +21,13 @@ open class ProgramExecution: CustomStringConvertible {
var error: Error?
var debug: DebugLevel = DebugLevel.Error
init(copy: ProgramExecution) {
self.scopes = copy.scopes
self.initialValues = copy.initialValues
self.error = copy.error
self.debug = copy.debug
}
public init() {
initialValues = .none
}
@@ -42,7 +49,7 @@ open class ProgramExecution: CustomStringConvertible {
}
public func setError(error: Error) -> ProgramExecution {
let npe = self
let npe = ProgramExecution(copy: self)
npe.error = error
return npe
}
@@ -52,7 +59,7 @@ open class ProgramExecution: CustomStringConvertible {
}
public func setDebugLevel(_ dl: DebugLevel) -> ProgramExecution {
let pe = self
let pe = ProgramExecution(copy: self)
pe.debug = dl
return pe
}
@@ -67,22 +74,22 @@ open class ProgramExecution: CustomStringConvertible {
}
public func enter_scope() -> ProgramExecution {
let new_pe = self
new_pe.scopes = self.scopes.enter()
let new_pe = ProgramExecution(copy: self)
new_pe.scopes = new_pe.scopes.enter()
return new_pe
}
public func exit_scope() -> ProgramExecution {
let new_pe = self
new_pe.scopes = self.scopes.exit()
let new_pe = ProgramExecution(copy: self)
new_pe.scopes = new_pe.scopes.exit()
return new_pe
}
public func declare(identifier: Identifier, withValue value: P4Value) -> ProgramExecution {
let new_pe = self
let new_scopes = self.scopes.declare(identifier: identifier, withValue: value)
let new_pe = ProgramExecution(copy: self)
let new_scopes = new_pe.scopes.declare(identifier: identifier, withValue: value)
new_pe.scopes = new_scopes
return new_pe
@@ -98,3 +105,12 @@ public typealias VarValueScope = Scope<P4Value>
/// Scopes that resolves variable identifiers to their values.
public typealias VarValueScopes = Scopes<P4Value>
/// Indicate the control flow result of a particular statement.
public enum ControlFlow {
case Next
case Continue
case Break
case Return(P4Value?)
case Error
}
+4 -2
View File
@@ -28,8 +28,10 @@ public protocol EvaluatableStatement {
/// Evaluate a statement for a given execution
/// - Parameters
/// - execution: The execution context in which to evaluate the parser statement
/// - Returns: An updated execution after evaluating the parser statement
func evaluate(execution: ProgramExecution) -> ProgramExecution
/// - Returns: A tuple of
/// 1. Whether this statement affects control flow.
/// 2. An updated execution after evaluating the parser statement
func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution)
}
public protocol P4Type: CustomStringConvertible {
+318
View File
@@ -0,0 +1,318 @@
// 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
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 Result.Ok((ParameterList([]), context))
}
#RequireNodeType<Node, (ParameterList, CompilerContext)>(
node: node, type: "parameter_list", nice_type_name: "Parameter List")
var parameters: ParameterList = ParameterList([])
if node.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing parameter list component"))
}
currentChild = node.child(at: currentChildIdx)
if currentChild?.nodeType == "parameter_list" {
switch parameter_list_compiler(node: currentChild!, withContext: context) {
case .Ok(let (ps, _)):
parameters = ps
case .Error(let e): return Result.Error(e)
}
currentChildIdx += 1
currentChildIdxSafe += 1
}
// We may have moved nodes, check/reset currentChild.
if node.childCount < currentChildIdxSafe {
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 Result.Ok((parameters, context))
}
// If this is a comma, we skip it!
if currentChild?.text == "," {
currentChildIdx += 1
currentChildIdxSafe += 1
}
if node.childCount < currentChildIdxSafe {
return Result.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 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 {
public typealias T = Parameter
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(Parameter, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
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
))
}
}
func argument_list_compiler(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(ArgumentList, CompilerContext)> {
var currentChildIdx = 0
var currentChildIdxSafe = 1
var currentChild: Node? = .none
if node.text == ")" {
// There are no arguments!
return Result.Ok((ArgumentList([]), context))
}
#RequireNodeType<Node, (ArgumentList, CompilerContext)>(
node: node, type: "argument_list", nice_type_name: "argument List")
var arguments: ArgumentList = ArgumentList([])
if node.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing argument list component"))
}
currentChild = node.child(at: currentChildIdx)
if currentChild?.nodeType == "argument_list" {
switch argument_list_compiler(node: currentChild!, withContext: context) {
case .Ok(let (ps, _)):
arguments = ps
case .Error(let e): return Result.Error(e)
}
currentChildIdx += 1
currentChildIdxSafe += 1
}
// We may have moved nodes, check/reset currentChild.
if node.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing argument list component"))
}
currentChild = node.child(at: currentChildIdx)
// If this is a ')', we are done.
if currentChild?.text == ")" {
return Result.Ok((arguments, context))
}
// If this is a comma, we skip it!
if currentChild?.text == "," {
currentChildIdx += 1
currentChildIdxSafe += 1
}
if node.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing argument list component"))
}
currentChild = node.child(at: currentChildIdx)
// Otherwise, there should be one argument left!
switch Argument.Compile(node: currentChild!, withContext: context) {
case .Ok(let (ce, updated_context)):
return Result.Ok((arguments.addArgument(Argument(ce, atIndex: arguments.count() + 1)), updated_context))
case .Error(let e): return Result.Error(e)
}
}
extension ArgumentList: Compilable {
public typealias T = ArgumentList
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(ArgumentList, CompilerContext)> {
let argument_node = node
#RequireNodeType<Node, (ArgumentList, CompilerContext)>(
node: argument_node, type: "arguments", nice_type_name: "arguments")
var currentChildIdx = 0
var currentChildIdxSafe = 1
// Let's eat the '(' before we start ...
if argument_node.childCount < currentChildIdxSafe {
return .Error(
ErrorOnNode(node: argument_node, withError: "Missing '(' in argument list component"))
}
currentChildIdx += 1
currentChildIdxSafe += 1
if argument_node.childCount < currentChildIdxSafe {
return .Error(
ErrorOnNode(node: argument_node, withError: "Missing argument list component"))
}
let currentChild = argument_node.child(at: currentChildIdx)
return argument_list_compiler(node: currentChild!, withContext: context)
}
}
extension Argument: Compilable {
public typealias T = EvaluatableExpression
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(EvaluatableExpression, CompilerContext)> {
let argument_node = node
#RequireNodeType<Node, (EvaluatableExpression, CompilerContext)>(
node: argument_node, type: "argument", nice_type_name: "argument")
let expression_node = node.child(at: 0)!
return switch Expression.Compile(node: expression_node, withContext: context) {
case .Ok(let compiled_expression): .Ok((compiled_expression, context))
case .Error(let e): .Error(e)
}
}
}
-179
View File
@@ -379,185 +379,6 @@ extension P4Lang.Parser: CompilableDeclaration {
}
}
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 Result.Ok((ParameterList([]), context))
}
#RequireNodeType<Node, (ParameterList, CompilerContext)>(
node: node, type: "parameter_list", nice_type_name: "Parameter List")
var parameters: ParameterList = ParameterList([])
if node.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing parameter list component"))
}
currentChild = node.child(at: currentChildIdx)
if currentChild?.nodeType == "parameter_list" {
switch parameter_list_compiler(node: currentChild!, withContext: context) {
case .Ok(let (ps, _)):
parameters = ps
case .Error(let e): return Result.Error(e)
}
currentChildIdx += 1
currentChildIdxSafe += 1
}
// We may have moved nodes, check/reset currentChild.
if node.childCount < currentChildIdxSafe {
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 Result.Ok((parameters, context))
}
// If this is a comma, we skip it!
if currentChild?.text == "," {
currentChildIdx += 1
currentChildIdxSafe += 1
}
if node.childCount < currentChildIdxSafe {
return Result.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 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 {
public typealias T = Parameter
public static func Compile(
node: Node, withContext context: CompilerContext
) -> Result<(Parameter, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
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
))
}
}
extension Control: CompilableDeclaration {
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
+65
View File
@@ -163,6 +163,7 @@ struct Expression {
let expression_parsers: [CompilableExpression.Type] = [
P4BooleanValue.self, P4StringValue.self, P4IntValue.self, TypedIdentifier.self,
BinaryOperatorExpression.self, ArrayAccessExpression.self, FieldAccessExpression.self,
FunctionCall.self
]
for candidate_expression_parser in expression_parsers {
@@ -653,3 +654,67 @@ extension ArrayAccessExpression: CompilableLValueExpression {
return Result.Ok(array_access_expression)
}
}
extension FunctionCall: CompilableExpression {
static func compile(
node: Node, withContext context: CompilerContext
) -> Result<EvaluatableExpression?> {
let expression = node.child(at: 0)!
#SkipUnlessNodeType<Node, EvaluatableExpression?>(
node: expression, type: "function_call")
var currentChildIdx = 0
var currentChildIdxSafe = 1
var currentChild: Node? = .none
if expression.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing function call component"))
}
currentChild = expression.child(at: currentChildIdx)
let maybe_callee_name = Identifier.Compile(
node: currentChild!, withContext: context)
guard case .Ok(let callee_name) = maybe_callee_name else {
return Result.Error(maybe_callee_name.error()!)
}
let maybe_callee = switch context.types.lookup(identifier: callee_name) {
case .Ok(let looked_up): switch looked_up {
case let callee as FunctionDeclaration: Result.Ok(callee) // What we found is actually a function declaration
default: Result<FunctionDeclaration>.Error(ErrorOnNode(node: currentChild!, withError: "\(callee_name) is not a function"))
}
case .Error(let e): Result<FunctionDeclaration>.Error(e)
}
guard case .Ok(let callee) = maybe_callee else {
return .Error(maybe_callee.error()!)
}
currentChildIdx += 1
currentChildIdxSafe += 1
if expression.childCount < currentChildIdxSafe {
return Result.Error(
ErrorOnNode(node: node, withError: "Missing function call component"))
}
currentChild = expression.child(at: currentChildIdx)
let maybe_argument_list = ArgumentList.Compile(node: currentChild!, withContext: context)
guard case .Ok((let arguments, _)) = maybe_argument_list else {
return .Error(maybe_argument_list.error()!)
}
// Now, compare the arguments with the parameters:
if case .Error(let e) = arguments.compatible(callee.params) {
return .Error(e)
}
// All good!
return .Ok(FunctionCall(callee, withArguments: arguments))
}
}
+1
View File
@@ -62,6 +62,7 @@ public struct Parser {
"expressionStatement": ExpressionStatement.self,
"variableDeclaration": VariableDeclarationStatement.self,
"conditionalStatement": ConditionalStatement.self, "blockStatement": BlockStatement.self,
"return_statement": ReturnStatement.self,
]
guard let parser = statementParsers[statement.nodeType ?? ""] else {
return Result.Error(
+16
View File
@@ -299,3 +299,19 @@ extension ParserAssignmentStatement: CompilableStatement {
))
}
}
extension ReturnStatement: CompilableStatement {
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(any Common.EvaluatableStatement, CompilerContext)> {
#RequireNodeType<Node, (EvaluatableStatement, CompilerContext)>(
node: node, type: "return_statement", nice_type_name: "return statement")
let expression_node = node.child(at: 1)!
return switch Expression.Compile(node: expression_node, withContext: context) {
case .Ok(let result): .Ok((ReturnStatement(result), context))
case .Error(let e): .Error(e)
}
}
}
+120
View File
@@ -0,0 +1,120 @@
// 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 Parameter: CustomStringConvertible, Equatable {
public static func == (lhs: Parameter, rhs: Parameter) -> Bool {
return lhs.name == rhs.name && lhs.type.eq(rhs: rhs.type)
}
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, Equatable {
public static func == (lhs: ParameterList, rhs: ParameterList) -> Bool {
if lhs.parameters.count != rhs.parameters.count {
return false
}
return 0
== zip(lhs.parameters, rhs.parameters).count { (lparam, rparam) in
return lparam != rparam
}
}
public var parameters: [Parameter]
public init() {
self.parameters = Array()
}
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)"
}
}
public struct ArgumentList {
public let arguments: [Argument]
public init(_ arguments: [Argument]) {
self.arguments = arguments
}
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.index
let arg_type = arg.argument.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(())
}
public func addArgument(_ argument: Argument) -> ArgumentList {
return ArgumentList(self.arguments + [argument])
}
public func count() -> Int {
return self.arguments.count
}
}
public struct Argument {
public let index: Int
public let argument: EvaluatableExpression
public init(_ argument: EvaluatableExpression, atIndex index: Int) {
self.argument = argument
self.index = index
}
}
-54
View File
@@ -19,60 +19,6 @@ import Common
public struct Declaration {}
public struct Parameter: CustomStringConvertible, Equatable {
public static func == (lhs: Parameter, rhs: Parameter) -> Bool {
return lhs.name == rhs.name && lhs.type.eq(rhs: rhs.type)
}
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, Equatable {
public static func == (lhs: ParameterList, rhs: ParameterList) -> Bool {
if lhs.parameters.count != rhs.parameters.count {
return false
}
return 0
== zip(lhs.parameters, rhs.parameters).count { (lparam, rparam) in
return lparam != rparam
}
}
public var parameters: [Parameter]
public init() {
self.parameters = Array()
}
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)"
}
}
public struct FunctionDeclaration: P4Type, P4Value {
public func type() -> any Common.P4Type {
return self
+7 -28
View File
@@ -201,33 +201,12 @@ public struct FieldAccessExpression {
}
}
public struct ArgumentList {
public let arguments: [(Int, EvaluatableExpression)]
public init(_ arguments: [EvaluatableExpression]) {
self.arguments = zip(1..., arguments).map { (idx, argument) in
(idx, argument)
}
}
public struct FunctionCall {
public let callee: FunctionDeclaration
public let arguments: ArgumentList
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(())
public init(_ callee: FunctionDeclaration, withArguments arguments: ArgumentList) {
self.callee = callee
self.arguments = arguments
}
}
}
+8
View File
@@ -55,3 +55,11 @@ public struct BlockStatement {
}
}
public struct ReturnStatement {
public let value: EvaluatableExpression
public init(_ value: EvaluatableExpression) {
self.value = value
}
}
+19
View File
@@ -0,0 +1,19 @@
// 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
+37
View File
@@ -382,3 +382,40 @@ extension KeysetExpression: EvaluatableExpression {
return self.kse_type()
}
}
extension FunctionCall: EvaluatableExpression {
public func evaluate(execution: Common.ProgramExecution) -> Common.Result<any Common.P4Value> {
guard let body = self.callee.body else {
return .Error(Error(withMessage: "No body for called function (\(self.callee.name))"))
}
// Put the arguments into scope
var called_execution = execution.enter_scope()
for (parameter, argument) in zip(self.callee.params.parameters, arguments.arguments) {
let arg_idx = argument.index
let arg_value = argument.argument
let maybe_argument_value = arg_value.evaluate(execution: called_execution)
guard case .Ok(let argument_value) = maybe_argument_value else {
return .Error(Error(withMessage: "Cannot evaluate argument \(arg_idx): \(argument)"))
}
called_execution = called_execution.declare(identifier: parameter.name, withValue: argument_value)
}
let (control_flow, _) = body.evaluate(execution: called_execution)
return switch control_flow {
case ControlFlow.Return(let value): if let value = value {
.Ok(value)
} else {
.Error(Error(withMessage: "No value returned from called function (\(self.callee.name))"))
}
default: .Error(Error(withMessage: "No value returned from called function (\(self.callee.name))"))
}
}
public func type() -> any Common.P4Type {
return self.callee.tipe
}
}
+18 -8
View File
@@ -19,20 +19,20 @@ import Common
import P4Lang
extension ParserAssignmentStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> ProgramExecution {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
let result = self.value.evaluate(execution: execution)
guard case Result.Ok(let value) = result else {
return execution.setError(error: result.error()!)
return (ControlFlow.Error, execution.setError(error: result.error()!))
}
let maybe_updated_scopes = self.lvalue.set(
to: value, inScopes: execution.scopes, duringExecution: execution)
guard case Result.Ok(let updated_scopes) = maybe_updated_scopes else {
return execution.setError(error: maybe_updated_scopes.error()!)
return (ControlFlow.Error, execution.setError(error: maybe_updated_scopes.error()!))
}
execution.scopes = updated_scopes.0
return execution
return (ControlFlow.Next, execution)
}
}
@@ -43,7 +43,12 @@ extension ParserStateDirectTransition: EvaluatableParserState {
var program = program.enter_scope()
for statement in statements {
program = statement.evaluate(execution: program)
let (control_flow, next_program) = statement.evaluate(execution: program)
switch control_flow {
case .Next: program = next_program // Ok!
case .Error: return (reject, next_program)
default: return (reject, next_program.setError(error: Error(withMessage: "Invalid control flow (\(control_flow) in parser)")))
}
}
let res = program.scopes.lookup(identifier: get_next_state())
@@ -93,7 +98,12 @@ extension ParserStateSelectTransition: EvaluatableParserState {
// First, evaluate the statements.
for statement in statements {
program = statement.evaluate(execution: program)
let (control_flow, next_program) = statement.evaluate(execution: program)
switch control_flow {
case .Next: program = next_program // Ok!
case .Error: return (reject, next_program)
default: return (reject, next_program.setError(error: Error(withMessage: "Invalid control flow (\(control_flow) in parser)")))
}
}
let res = self.selectExpression.evaluate(execution: program)
@@ -159,8 +169,8 @@ extension Parser: CallableExecution {
}
for (parameter, argument) in zip(self.parameters.parameters, arguments.arguments) {
let arg_idx = argument.0
let arg_value = argument.1
let arg_idx = argument.index
let arg_value = argument.argument
let maybe_argument_value = arg_value.evaluate(execution: execution)
guard case .Ok(let argument_value) = maybe_argument_value else {
return (
-6
View File
@@ -22,12 +22,6 @@ public protocol Execution {
func execute(execution: ProgramExecution) -> ProgramExecution
}
public protocol Compilable {
associatedtype ToCompile
associatedtype Compiled
static func compile(_: ToCompile) -> Result<Compiled>
}
public protocol EvaluatableParserState: P4Value {
func execute(program: ProgramExecution) -> (EvaluatableParserState, ProgramExecution)
func done() -> Bool
+66 -18
View File
@@ -19,51 +19,99 @@ import Common
import P4Lang
extension BlockStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> ProgramExecution {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
var execution = execution
for s in self.statements {
execution = s.evaluate(execution: execution)
let (control_flow, next_execution) = s.evaluate(execution: execution)
switch control_flow {
case ControlFlow.Return(let value): return (ControlFlow.Return(value), next_execution)
case ControlFlow.Next: execution = next_execution
case ControlFlow.Error: return (ControlFlow.Error, next_execution)
default:
return (
ControlFlow.Next,
next_execution.setError(
error: Error(withMessage: "Invalid control flow \(control_flow) in block statement"))
)
}
}
return execution
return (ControlFlow.Next, execution)
}
}
extension VariableDeclarationStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> ProgramExecution {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
guard case .Ok(let initial_value) = self.initializer.evaluate(execution: execution) else {
return execution.setError(error: Error(withMessage: "Could not evaluate \(self.initializer)"))
return (
ControlFlow.Error,
execution.setError(error: Error(withMessage: "Could not evaluate \(self.initializer)"))
)
}
let new_scopes = execution.scopes.declare(identifier: self.identifier, withValue: initial_value)
execution.scopes = new_scopes
return execution
return (ControlFlow.Next, execution)
}
}
extension ConditionalStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> ProgramExecution {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
guard case .Ok(let evaluated_condition) = self.condition.evaluate(execution: execution) else {
return execution.setError(error: Error(withMessage: "Could not evaluate \(self.condition)"))
return (
ControlFlow.Error,
execution.setError(error: Error(withMessage: "Could not evaluate \(self.condition)"))
)
}
if !evaluated_condition.type().eq(rhs: P4Boolean()) {
return execution.setError(error: Error(withMessage: "Condition expression is not a Boolean"))
return (
ControlFlow.Error,
execution.setError(error: Error(withMessage: "Condition expression is not a Boolean"))
)
}
if evaluated_condition.eq(rhs: P4BooleanValue.init(withValue: true)) {
let execution = execution.enter_scope()
var result = self.thenn.evaluate(execution: execution)
result = result.exit_scope()
return result
switch self.thenn.evaluate(execution: execution) {
case (ControlFlow.Next, let result): return (ControlFlow.Next, result.exit_scope())
case (ControlFlow.Error, let result): return (ControlFlow.Error, result.exit_scope())
case (let cf, let result):
return (
ControlFlow.Next,
result.setError(
error: Error(withMessage: "Invalid control flow \(cf) in conditional statement"))
)
}
} else if let elss = self.elss {
let execution = execution.enter_scope()
var result = elss.evaluate(execution: execution)
result = result.exit_scope()
return result
switch elss.evaluate(execution: execution) {
case (ControlFlow.Next, let result): return (ControlFlow.Next, result.exit_scope())
case (ControlFlow.Error, let result): return (ControlFlow.Error, result.exit_scope())
case (let cf, let result):
return (
ControlFlow.Next,
result.setError(
error: Error(withMessage: "Invalid control flow \(cf) in conditional statement"))
)
}
}
return execution
return (ControlFlow.Next, execution)
}
}
extension ExpressionStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> ProgramExecution {
return execution
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
// TODO: Should this do something? Side effects?
return (ControlFlow.Next, execution)
}
}
extension ReturnStatement: EvaluatableStatement {
public func evaluate(execution: ProgramExecution) -> (ControlFlow, ProgramExecution) {
return switch self.value.evaluate(execution: execution) {
case .Ok(let v): (ControlFlow.Return(v), execution)
case .Error(let e): (ControlFlow.Error, execution.setError(error: e))
}
}
}
@@ -0,0 +1,136 @@
// 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 P4Lang
import P4Runtime
import SwiftTreeSitter
import Testing
import TreeSitter
import TreeSitterP4
@testable import P4Compiler
@Test func test_function_call_scoped_name_collision() async throws {
let simple_parser_declaration = """
bool functionb(bool c) {
return c;
};
parser main_parser() {
state start {
int c = 5;
bool b = functionb(true);
transition select (b) {
false: reject;
true: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_function_call_scoped_name_collision2() async throws {
// Test whether the assignment to c leaks out of the function call scope.
let simple_parser_declaration = """
bool functionb(bool c) {
c = true;
return c;
};
parser main_parser() {
state start {
bool c = false;
bool b = functionb(true);
transition select (c) {
false: reject;
true: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@Test func test_function_call_integer_return_value() async throws {
let simple_parser_declaration = """
int functionb(int c) {
return c;
};
parser main_parser() {
state start {
int c = 5;
transition select (5 == functionb(c)) {
false: reject;
true: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_function_call_integer_return_value2() async throws {
let simple_parser_declaration = """
int functionb(int c) {
return c;
};
parser main_parser() {
state start {
int c = 5;
transition select (4 == functionb(c)) {
false: reject;
true: accept;
};
}
};
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let parser = try #UseOkResult(program.find_parser(withName: Identifier(name: "main_parser")))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(parser.states.count() == 1)
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
@@ -176,7 +176,7 @@ import TreeSitterP4
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([
P4BooleanValue(withValue: false), P4StringValue(withValue: "Testing"), P4IntValue(withValue: 5),
Argument(P4BooleanValue(withValue: false), atIndex: 1), Argument(P4StringValue(withValue: "Testing"), atIndex: 2), Argument(P4IntValue(withValue: 5), atIndex: 3),
])
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: args))
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
@@ -197,7 +197,7 @@ import TreeSitterP4
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([
P4BooleanValue(withValue: false), P4StringValue(withValue: "Testing"), P4IntValue(withValue: 5),
Argument(P4BooleanValue(withValue: false), atIndex: 1), Argument(P4StringValue(withValue: "Testing"), atIndex: 2), Argument(P4IntValue(withValue: 5), atIndex: 3),
])
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: args))
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
+4 -4
View File
@@ -96,7 +96,7 @@ import TreeSitterP4
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([
P4BooleanValue(withValue: true), P4StringValue(withValue: "Testing"), P4IntValue(withValue: 5),
Argument(P4BooleanValue(withValue: true), atIndex: 1), Argument(P4StringValue(withValue: "Testing"), atIndex: 2), Argument(P4IntValue(withValue: 5), atIndex: 3),
])
let (state_result, _) = try! #UseOkResult(runtime.run(withArguments: args))
// We should be in the accept state.
@@ -118,7 +118,7 @@ import TreeSitterP4
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([
P4BooleanValue(withValue: true), P4BooleanValue(withValue: false), P4IntValue(withValue: 5),
Argument(P4BooleanValue(withValue: true), atIndex: 1), Argument(P4BooleanValue(withValue: false), atIndex: 2), Argument(P4IntValue(withValue: 5), atIndex: 3),
])
#expect(
@@ -142,7 +142,7 @@ import TreeSitterP4
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([
P4IntValue(withValue: 5), P4StringValue(withValue: "Testing"), P4IntValue(withValue: 5),
Argument(P4IntValue(withValue: 5), atIndex: 1), Argument(P4StringValue(withValue: "Testing"), atIndex: 2), Argument(P4IntValue(withValue: 5), atIndex: 3),
])
#expect(
@@ -164,7 +164,7 @@ import TreeSitterP4
"""
let program = try #UseOkResult(Program.Compile(simple_parser_declaration))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let args = ArgumentList([P4BooleanValue(withValue: true)])
let args = ArgumentList([Argument(P4BooleanValue(withValue: true), atIndex: 0)])
#expect(
#RequireErrorResult<(ParserState, ProgramExecution)>(
+11 -2
View File
@@ -34,6 +34,10 @@ export default grammar({
direction: $ => choice($.in, $.out, $.inout),
parameters: $=> seq('(', optional($.parameter_list), ')'),
argument_list: $ => choice($.argument, seq($.argument_list, ',', $.argument)),
argument: $ => $.expression,
arguments: $=> seq('(', optional($.argument_list), ')'),
// Common - Types
typeRef: $ => choice($.baseType, $.type_identifier),
baseType: $ => choice($.bool, $.error, $.string, $.int, $.bit /* omitting "templated" types" */),
@@ -96,11 +100,12 @@ export default grammar({
// General statements
statements: $ => repeat1($.statement),
statement: $ => choice($.conditionalStatement, $.blockStatement, $.expressionStatement, $.assignmentStatement, $.variableDeclaration),// Limited, so far.
statement: $ => choice($.conditionalStatement, $.blockStatement, $.expressionStatement, $.assignmentStatement, $.variableDeclaration, $.return_statement),// Limited, so far.
blockStatement: $ => seq(optional($.annotations), '{', optional($.statements), '}'),
conditionalStatement: $ => choice(prec(1, seq($.if, '(', $.expression, ')', $.statement)), prec(2, seq($.if, '(', $.expression, ')', $.statement, $.else, $.statement))),
expressionStatement: $=> seq($.expression, $._semicolon),
assignmentStatement: $=> seq($.expression, $.assignment, $.expression, $._semicolon),
return_statement: $=> seq($.return, $.expression, $._semicolon),
// Parser statements
parserStatements: $ => repeat1($.parserStatement),
@@ -111,7 +116,7 @@ export default grammar({
// Expressions
expression: $ => choice($.grouped_expression, $.simple_expression),
grouped_expression: $ => seq('(', $.expression, ')'),
simple_expression: $ => choice($.identifier, $.integer, $.booleanLiteralExpression, $.string_literal, $.binaryOperatorExpression, $.arrayAccessExpression, $.fieldAccessExpression), // Very limited.
simple_expression: $ => choice($.identifier, $.integer, $.booleanLiteralExpression, $.string_literal, $.binaryOperatorExpression, $.arrayAccessExpression, $.fieldAccessExpression, $.function_call), // Very limited.
booleanLiteralExpression: $ => choice($.true, $.false),
selectExpression: $ => seq($.select, '(', $.expression, ')', '{', $.selectBody, '}'), // TODO: Should be expression list and not just a single expression
transitionSelectionExpression: $ => choice($.identifier, $.selectExpression),
@@ -131,6 +136,10 @@ export default grammar({
arrayAccessExpression: $ => seq($.expression, $.open_bracket, $.expression, $.close_bracket),
fieldAccessExpression: $=> prec.left(2, seq($.expression, $.field_access, $.identifier)),
// Function call
function_call: $=> seq($.identifier, $.arguments),
// Binary Operations
binaryEqualOperatorExpression: $ => prec.left(2, seq($.expression, $.double_equal, $.expression)),
binaryLessThanOperatorExpression: $ => prec.left(2, seq($.expression, $.less_than, $.expression)),
+128
View File
@@ -306,3 +306,131 @@ parser simple() {
)
)
)
=========================
Simple Function Call
=========================
parser simple() {
state start {
func();
transition accept;
}
};
---
(p4program
(declaration
(parserDeclaration
(parserType
(parser)
(identifier)
(parameters)
)
(parserStates
(parserState
(state)
(identifier)
(parserStatements
(parserStatement
(expressionStatement
(expression
(simple_expression
(function_call
(identifier)
(arguments)
)
)
)
)
)
)
(parserTransitionStatement
(transition)
(transitionSelectionExpression
(identifier)
)
)
)
)
)
)
)
=========================
Simple Function Call (With Arguments)
=========================
parser simple() {
state start {
func(1, true, variable);
transition accept;
}
};
---
(p4program
(declaration
(parserDeclaration
(parserType
(parser)
(identifier)
(parameters)
)
(parserStates
(parserState
(state)
(identifier)
(parserStatements
(parserStatement
(expressionStatement
(expression
(simple_expression
(function_call
(identifier)
(arguments
(argument_list
(argument_list
(argument_list
(argument
(expression
(simple_expression
(integer)
)
)
)
)
(argument
(expression
(simple_expression
(booleanLiteralExpression
(true)
)
)
)
)
)
(argument
(expression
(simple_expression
(identifier)
)
)
)
)
)
)
)
)
)
)
)
(parserTransitionStatement
(transition)
(transitionSelectionExpression
(identifier)
)
)
)
)
)
)
)