From d39127ac17420a556f8b70943a5efa5adfb6295e Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Thu, 9 Apr 2026 23:16:27 -0400 Subject: [PATCH] Support Function Calls Signed-off-by: Will Hawkins --- Sources/Common/Execution.swift | 32 +- Sources/Common/Protocols.swift | 6 +- Sources/P4Compiler/Common.swift | 318 ++++++++++++++++++ Sources/P4Compiler/Declarations.swift | 179 ---------- Sources/P4Compiler/Expression.swift | 65 ++++ Sources/P4Compiler/Parser.swift | 1 + Sources/P4Compiler/Statement.swift | 16 + Sources/P4Lang/Common.swift | 120 +++++++ Sources/P4Lang/Declarations.swift | 54 --- Sources/P4Lang/Expressions.swift | 35 +- Sources/P4Lang/Statement.swift | 8 + Sources/P4Runtime/Common.swift | 19 ++ Sources/P4Runtime/Expressions.swift | 37 ++ Sources/P4Runtime/Parser.swift | 26 +- Sources/P4Runtime/Protocols.swift | 6 - Sources/P4Runtime/Statements.swift | 84 ++++- .../ExpressionTests/FunctionCall.swift | 136 ++++++++ .../ExpressionTests/SelectExpression.swift | 4 +- Tests/p4rseTests/RuntimeTests.swift | 8 +- tree-sitter-p4/grammar.js | 13 +- tree-sitter-p4/test/corpus/expressions.txt | 128 +++++++ 21 files changed, 984 insertions(+), 311 deletions(-) create mode 100644 Sources/P4Compiler/Common.swift create mode 100644 Sources/P4Lang/Common.swift create mode 100644 Sources/P4Runtime/Common.swift create mode 100644 Tests/p4rseTests/ExpressionTests/FunctionCall.swift diff --git a/Sources/Common/Execution.swift b/Sources/Common/Execution.swift index e977281..ce9968d 100644 --- a/Sources/Common/Execution.swift +++ b/Sources/Common/Execution.swift @@ -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 /// Scopes that resolves variable identifiers to their values. public typealias VarValueScopes = Scopes + +/// Indicate the control flow result of a particular statement. +public enum ControlFlow { + case Next + case Continue + case Break + case Return(P4Value?) + case Error +} diff --git a/Sources/Common/Protocols.swift b/Sources/Common/Protocols.swift index cc802f5..6800ce2 100644 --- a/Sources/Common/Protocols.swift +++ b/Sources/Common/Protocols.swift @@ -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 { diff --git a/Sources/P4Compiler/Common.swift b/Sources/P4Compiler/Common.swift new file mode 100644 index 0000000..3b2725a --- /dev/null +++ b/Sources/P4Compiler/Common.swift @@ -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 . + +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: 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: 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: 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: 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: 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: 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) + } + } +} \ No newline at end of file diff --git a/Sources/P4Compiler/Declarations.swift b/Sources/P4Compiler/Declarations.swift index 0097eb7..773eb34 100644 --- a/Sources/P4Compiler/Declarations.swift +++ b/Sources/P4Compiler/Declarations.swift @@ -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: 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: 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: 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 diff --git a/Sources/P4Compiler/Expression.swift b/Sources/P4Compiler/Expression.swift index 09ae7a6..fc3eb85 100644 --- a/Sources/P4Compiler/Expression.swift +++ b/Sources/P4Compiler/Expression.swift @@ -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 { + let expression = node.child(at: 0)! + #SkipUnlessNodeType( + 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.Error(ErrorOnNode(node: currentChild!, withError: "\(callee_name) is not a function")) + } + case .Error(let e): Result.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)) + } +} diff --git a/Sources/P4Compiler/Parser.swift b/Sources/P4Compiler/Parser.swift index 1969ab8..9fa225a 100644 --- a/Sources/P4Compiler/Parser.swift +++ b/Sources/P4Compiler/Parser.swift @@ -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( diff --git a/Sources/P4Compiler/Statement.swift b/Sources/P4Compiler/Statement.swift index fa60e72..de65988 100644 --- a/Sources/P4Compiler/Statement.swift +++ b/Sources/P4Compiler/Statement.swift @@ -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: 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) + } + } +} \ No newline at end of file diff --git a/Sources/P4Lang/Common.swift b/Sources/P4Lang/Common.swift new file mode 100644 index 0000000..59fe267 --- /dev/null +++ b/Sources/P4Lang/Common.swift @@ -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 . + +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 + } +} \ No newline at end of file diff --git a/Sources/P4Lang/Declarations.swift b/Sources/P4Lang/Declarations.swift index 92b78bf..69fddc9 100644 --- a/Sources/P4Lang/Declarations.swift +++ b/Sources/P4Lang/Declarations.swift @@ -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 diff --git a/Sources/P4Lang/Expressions.swift b/Sources/P4Lang/Expressions.swift index 51c7f98..ba7a884 100644 --- a/Sources/P4Lang/Expressions.swift +++ b/Sources/P4Lang/Expressions.swift @@ -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 } -} +} \ No newline at end of file diff --git a/Sources/P4Lang/Statement.swift b/Sources/P4Lang/Statement.swift index e8c82d4..a903cd8 100644 --- a/Sources/P4Lang/Statement.swift +++ b/Sources/P4Lang/Statement.swift @@ -55,3 +55,11 @@ public struct BlockStatement { } } + +public struct ReturnStatement { + public let value: EvaluatableExpression + + public init(_ value: EvaluatableExpression) { + self.value = value + } +} \ No newline at end of file diff --git a/Sources/P4Runtime/Common.swift b/Sources/P4Runtime/Common.swift new file mode 100644 index 0000000..604a61a --- /dev/null +++ b/Sources/P4Runtime/Common.swift @@ -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 . + +import Common +import P4Lang \ No newline at end of file diff --git a/Sources/P4Runtime/Expressions.swift b/Sources/P4Runtime/Expressions.swift index d28aede..63dad0a 100644 --- a/Sources/P4Runtime/Expressions.swift +++ b/Sources/P4Runtime/Expressions.swift @@ -382,3 +382,40 @@ extension KeysetExpression: EvaluatableExpression { return self.kse_type() } } + +extension FunctionCall: EvaluatableExpression { + public func evaluate(execution: Common.ProgramExecution) -> Common.Result { + + 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 + } +} diff --git a/Sources/P4Runtime/Parser.swift b/Sources/P4Runtime/Parser.swift index 148d255..fdee834 100644 --- a/Sources/P4Runtime/Parser.swift +++ b/Sources/P4Runtime/Parser.swift @@ -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 ( diff --git a/Sources/P4Runtime/Protocols.swift b/Sources/P4Runtime/Protocols.swift index 01a6bc3..eecb3c4 100644 --- a/Sources/P4Runtime/Protocols.swift +++ b/Sources/P4Runtime/Protocols.swift @@ -22,12 +22,6 @@ public protocol Execution { func execute(execution: ProgramExecution) -> ProgramExecution } -public protocol Compilable { - associatedtype ToCompile - associatedtype Compiled - static func compile(_: ToCompile) -> Result -} - public protocol EvaluatableParserState: P4Value { func execute(program: ProgramExecution) -> (EvaluatableParserState, ProgramExecution) func done() -> Bool diff --git a/Sources/P4Runtime/Statements.swift b/Sources/P4Runtime/Statements.swift index 5e1a345..c7acc9c 100644 --- a/Sources/P4Runtime/Statements.swift +++ b/Sources/P4Runtime/Statements.swift @@ -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)) + } + } +} + + diff --git a/Tests/p4rseTests/ExpressionTests/FunctionCall.swift b/Tests/p4rseTests/ExpressionTests/FunctionCall.swift new file mode 100644 index 0000000..35688ac --- /dev/null +++ b/Tests/p4rseTests/ExpressionTests/FunctionCall.swift @@ -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 . + +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) +} diff --git a/Tests/p4rseTests/ExpressionTests/SelectExpression.swift b/Tests/p4rseTests/ExpressionTests/SelectExpression.swift index 95bb15c..0ab4d08 100644 --- a/Tests/p4rseTests/ExpressionTests/SelectExpression.swift +++ b/Tests/p4rseTests/ExpressionTests/SelectExpression.swift @@ -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) diff --git a/Tests/p4rseTests/RuntimeTests.swift b/Tests/p4rseTests/RuntimeTests.swift index 5ef46d3..83ef97c 100644 --- a/Tests/p4rseTests/RuntimeTests.swift +++ b/Tests/p4rseTests/RuntimeTests.swift @@ -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)>( diff --git a/tree-sitter-p4/grammar.js b/tree-sitter-p4/grammar.js index 83e25ff..0089805 100644 --- a/tree-sitter-p4/grammar.js +++ b/tree-sitter-p4/grammar.js @@ -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)), diff --git a/tree-sitter-p4/test/corpus/expressions.txt b/tree-sitter-p4/test/corpus/expressions.txt index 048c1fb..99cd553 100644 --- a/tree-sitter-p4/test/corpus/expressions.txt +++ b/tree-sitter-p4/test/corpus/expressions.txt @@ -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) + ) + ) + ) + ) + ) + ) +)