compiler, runtime, common: (Initial) Support For extern Declarations

Especially FFI

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
This commit is contained in:
Will Hawkins
2026-04-23 06:07:07 -04:00
parent 74fead1eba
commit f2bd53ce5f
17 changed files with 693 additions and 247 deletions
+134
View File
@@ -0,0 +1,134 @@
// 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/>.
public struct Parameter: CustomStringConvertible, Equatable {
public static func == (lhs: Parameter, rhs: Parameter) -> Bool {
return lhs.name == rhs.name && lhs.type.eq(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)"
}
/// Calculate whether the `argument` is compatible with this parameter.
public func compatible(_ argument: Argument) -> Bool {
let arg_type = argument.argument.type()
// If the parameter is (in)out, then the argument must be an lvalue.
if let param_direction = self.type.direction(),
param_direction == Direction.In || param_direction == Direction.InOut
{
if !(argument.argument is EvaluatableLValueExpression) {
return false
}
}
return arg_type.dataType().eq(rhs: self.type.dataType())
}
}
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 !param.compatible(arg) {
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
}
}
+22
View File
@@ -0,0 +1,22 @@
// 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/>.
public protocol P4FFI {
func execute(execution: ProgramExecution) -> (ControlFlow, ProgramExecution)
func type() -> P4Type
func parameters() -> ParameterList
}
+64 -13
View File
@@ -52,60 +52,111 @@ public func ErrorOnNode(node: Node, withError error: String) -> Error {
public struct CompilerContext { public struct CompilerContext {
let instances: VarTypeScopes let instances: VarTypeScopes
let types: TypeTypeScopes let types: TypeTypeScopes
let externs: TypeTypeScopes
let ffis: [P4FFI]
let expected_type: P4Type? let expected_type: P4Type?
let extern_context: Bool
public init(withInstances _instances: VarTypeScopes) { public init() {
instances = _instances instances = VarTypeScopes().enter()
types = TypeTypeScopes() types = TypeTypeScopes().enter()
externs = TypeTypeScopes().enter()
expected_type = .none expected_type = .none
extern_context = false
ffis = Array()
} }
public init(withInstances _instances: VarTypeScopes, withTypes _types: TypeTypeScopes) { public init(withInstances _instances: VarTypeScopes, withTypes _types: TypeTypeScopes) {
instances = _instances instances = _instances
types = _types types = _types
externs = TypeTypeScopes().enter()
expected_type = .none expected_type = .none
extern_context = false
ffis = Array()
} }
public init( public init(
withInstances _instances: VarTypeScopes, withTypes _types: TypeTypeScopes, withInstances _instances: VarTypeScopes, withTypes _types: TypeTypeScopes,
withExpectation expectation: P4Type? withExpectation expectation: P4Type?, withExtern extern: Bool,
withExterns externs: TypeTypeScopes, withFFIs foreigns: [P4FFI]
) { ) {
instances = _instances instances = _instances
types = _types types = _types
expected_type = expectation expected_type = expectation
extern_context = extern
self.externs = externs
ffis = foreigns
} }
/// Update a compiler context /// Update a compiler context
/// ///
/// Create a new compiler context based on the current with the same types and new names. /// Create a new compiler context based on the current but new instances.
/// ///
/// - Parameter instances: a ``VarTypeScopes`` with the updated instances for the newly created compiler context. /// - Parameter instances: a ``VarTypeScopes`` with the updated instances for the newly created compiler context.
/// - Returns: A new compiler context based on the current with the same types and new names. /// - Returns: A new compiler context based on the current but new instances.
public func update(newInstances instances: VarTypeScopes) -> CompilerContext { public func update(newInstances instances: VarTypeScopes) -> CompilerContext {
return CompilerContext( return CompilerContext(
withInstances: instances, withTypes: self.types, withExpectation: self.expected_type) withInstances: instances, withTypes: self.types, withExpectation: self.expected_type,
withExtern: self.extern_context, withExterns: self.externs, withFFIs: self.ffis)
} }
/// Update a compiler context /// Update a compiler context
/// ///
/// Create a new compiler context based on the current with the same names and new types. /// Create a new compiler context based on the current but new types.
/// ///
/// - Parameter types: a ``TypeTypeScopes`` with the updated types for the newly created compiler context. /// - Parameter types: a ``TypeTypeScopes`` with the updated types for the newly created compiler context.
/// - Returns: A new compiler context based on the current with the same names and new types. /// - Returns: A new compiler context based on the current but new types.
public func update(newTypes types: TypeTypeScopes) -> CompilerContext { public func update(newTypes types: TypeTypeScopes) -> CompilerContext {
return CompilerContext( return CompilerContext(
withInstances: self.instances, withTypes: types, withExpectation: self.expected_type) withInstances: self.instances, withTypes: types, withExpectation: self.expected_type,
withExtern: self.extern_context, withExterns: self.externs, withFFIs: self.ffis)
} }
/// Update a compiler context /// Update a compiler context
/// ///
/// Create a new compiler context based on the current with the same names and types but new expected type. /// Create a new compiler context based on the current but new expected type.
/// ///
/// - Parameter expectation: a ``P4Type?`` to (re)set the type the compiler is expecting. /// - Parameter expectation: a ``P4Type?`` to (re)set the type the compiler is expecting.
/// - Returns: A new compiler context based on the current with the same names and types but new expected type. /// - Returns: A new compiler context based on the current but new expected type.
public func update(newExpectation expectation: P4Type?) -> CompilerContext { public func update(newExpectation expectation: P4Type?) -> CompilerContext {
return CompilerContext( return CompilerContext(
withInstances: self.instances, withTypes: self.types, withExpectation: expectation) withInstances: self.instances, withTypes: self.types, withExpectation: expectation,
withExtern: self.extern_context, withExterns: self.externs, withFFIs: self.ffis)
} }
/// Update a compiler context
///
/// Create a new compiler context based on the current but new extern context value.
///
/// - Parameter extern: a ``Bool`` to (re)set whether the compiler is compiling in an extern context.
/// - Returns: A new compiler context based on the current but new extern context value.
public func update(newExtern extern: Bool) -> CompilerContext {
return CompilerContext(
withInstances: self.instances, withTypes: self.types, withExpectation: self.expected_type,
withExtern: extern, withExterns: self.externs, withFFIs: self.ffis)
}
/// Update a compiler context
///
/// Create a new compiler context based on the current but new externs.
///
/// - Parameter externs: a ``TypeTypeScopes`` to (re)set the list of extern-al declarations.
/// - Returns: A new compiler context based on the current but new list of external-al declarations.
public func update(newExterns externs: TypeTypeScopes) -> CompilerContext {
return CompilerContext(
withInstances: self.instances, withTypes: self.types, withExpectation: self.expected_type,
withExtern: self.extern_context, withExterns: externs, withFFIs: self.ffis)
}
/// Update a compiler context
///
/// Create a new compiler context based on the current but new FFIs.
///
/// - Parameter foreigns: an array of ``P4FFI`` to (re)set the list of foreign functions.
/// - Returns: A new compiler context based on the current but with a new list of foreign functions.
public func update(newFFIs foreigns: [P4FFI]) -> CompilerContext {
return CompilerContext(
withInstances: self.instances, withTypes: self.types, withExpectation: self.expected_type,
withExtern: self.extern_context, withExterns: externs, withFFIs: foreigns)
}
} }
+126 -36
View File
@@ -25,12 +25,13 @@ import TreeSitterP4
extension Declaration: CompilableDeclaration { extension Declaration: CompilableDeclaration {
public static func Compile( public static func Compile(
node: Node, withContext context: CompilerContext node: Node, withContext context: CompilerContext
) -> Result<(P4DataType, CompilerContext)?> { ) -> Result<(Declaration, CompilerContext)?> {
let declaration_compilers: [String: CompilableDeclaration.Type] = [ let declaration_compilers: [String: CompilableDeclaration.Type] = [
"function_declaration": FunctionDeclaration.self, "function_declaration": FunctionDeclaration.self,
"control_declaration": Control.self, "control_declaration": Control.self,
"type_declaration": StructDeclaration.self, // ASSUME: Type declarations are struct declarations. "type_declaration": P4Struct.self, // ASSUME: Type declarations are struct declarations.
"extern_declaration": ExternDeclaration.self,
] ]
guard let declaration_compiler = declaration_compilers[node.nodeType!] else { guard let declaration_compiler = declaration_compilers[node.nodeType!] else {
@@ -44,7 +45,7 @@ extension Declaration: CompilableDeclaration {
extension FunctionDeclaration: CompilableDeclaration { extension FunctionDeclaration: CompilableDeclaration {
public static func Compile( public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(any Common.P4DataType, CompilerContext)?> { ) -> Common.Result<(Declaration, CompilerContext)?> {
let function_declaration_node = node let function_declaration_node = node
#RequireNodeType<Node, (ParameterList, CompilerContext)>( #RequireNodeType<Node, (ParameterList, CompilerContext)>(
node: function_declaration_node, type: "function_declaration", node: function_declaration_node, type: "function_declaration",
@@ -96,13 +97,11 @@ extension FunctionDeclaration: CompilableDeclaration {
} }
context = updated_context context = updated_context
var function_body: BlockStatement? = .none
currentChildIdx += 1 currentChildIdx += 1
currentChildIdxSafe += 1 currentChildIdxSafe += 1
if function_declaration_node.childCount < currentChildIdxSafe { if currentChildIdxSafe <= function_declaration_node.childCount {
return Result.Error(
ErrorOnNode(
node: function_declaration_node, withError: "Missing function declaration component"))
}
currentChild = function_declaration_node.child(at: currentChildIdx) currentChild = function_declaration_node.child(at: currentChildIdx)
// Add the parameters into scope. // Add the parameters into scope.
@@ -112,36 +111,55 @@ extension FunctionDeclaration: CompilableDeclaration {
identifier: parameter.name, withValue: parameter.type) identifier: parameter.name, withValue: parameter.type)
} }
let maybe_function_body = Parser.Statement.Compile( let maybe_function_body = BlockStatement.Compile(
node: currentChild!, node: currentChild!,
withContext: context.update(newInstances: function_scope).update( withContext: context.update(newInstances: function_scope).update(
newExpectation: function_type)) newExpectation: function_type))
guard case .Ok((let function_body, _)) = maybe_function_body else {
guard case .Ok((let parsed_function_body, _)) = maybe_function_body else {
return .Error(maybe_function_body.error()!) return .Error(maybe_function_body.error()!)
} }
function_body = (parsed_function_body as! BlockStatement)
} else {
let function_declaration = FunctionDeclaration( // If we are in an extern context, no body is okay!
if !context.extern_context {
return Result.Error(
ErrorOnNode(
node: function_declaration_node, withError: "Missing function declaration component"))
}
}
let function_declaration = Declaration(
TypedIdentifier(
id: function_name,
withType: P4Type(
FunctionDeclaration(
named: function_name, ofType: function_type, withParameters: function_parameters, named: function_name, ofType: function_type, withParameters: function_parameters,
withBody: function_body) withBody: function_body))))
// Do not use the updated context returned by parsing the body // Do not use the updated context returned by parsing the body
// and do not use the function_scope, either. // and do not use the function_scope, either.
// And, do not update the context if we are compiling in an
// extern context.
return .Ok( return .Ok(
( (
function_declaration, function_declaration,
context.update( context.extern_context
? context
: context.update(
newTypes: context.types.declare( newTypes: context.types.declare(
identifier: function_name, withValue: function_declaration)) identifier: function_name, withValue: function_declaration.identifier.type.dataType())
)
)) ))
} }
} }
struct StructDeclaration {} extension P4Struct: CompilableDeclaration {
static public func Compile(
extension StructDeclaration: CompilableDeclaration {
static func Compile(
node: Node, withContext context: CompilerContext node: Node, withContext context: CompilerContext
) -> Result<(P4DataType, CompilerContext)?> { ) -> Result<(Declaration, CompilerContext)?> {
let struct_declaration_node = node.child(at: 0)! let struct_declaration_node = node.child(at: 0)!
var currentChildIdx = 0 var currentChildIdx = 0
@@ -195,12 +213,18 @@ extension StructDeclaration: CompilableDeclaration {
// If there are no fields, it will be a "}" // If there are no fields, it will be a "}"
if currentChild!.nodeType == "}" { if currentChild!.nodeType == "}" {
let struc = P4Struct(withName: struct_identifier, andFields: P4StructFields([])) let struc = Declaration(
TypedIdentifier(
id: struct_identifier,
withType: P4Type(P4Struct(withName: struct_identifier, andFields: P4StructFields([])))))
return Result.Ok( return Result.Ok(
( (
struc, struc,
context.update( context.extern_context
newTypes: context.types.declare(identifier: struct_identifier, withValue: struc)) ? context
: context.update(
newTypes: context.types.declare(
identifier: struct_identifier, withValue: struc.identifier.type.dataType()))
)) ))
} }
@@ -234,14 +258,20 @@ extension StructDeclaration: CompilableDeclaration {
}.joined(separator: ";")))) }.joined(separator: ";"))))
} }
let declared_struct = P4Struct( let declared_struct = Declaration(
withName: struct_identifier, andFields: P4StructFields(parsed_fields)) TypedIdentifier(
id: struct_identifier,
withType: P4Type(
P4Struct(
withName: struct_identifier, andFields: P4StructFields(parsed_fields)))))
return .Ok( return .Ok(
( (
declared_struct, declared_struct,
current_context.update( current_context.extern_context
? current_context
: current_context.update(
newTypes: current_context.types.declare( newTypes: current_context.types.declare(
identifier: struct_identifier, withValue: declared_struct)) identifier: struct_identifier, withValue: declared_struct.identifier.type.dataType()))
)) ))
} }
} }
@@ -249,7 +279,7 @@ extension StructDeclaration: CompilableDeclaration {
extension P4Lang.Parser: CompilableDeclaration { extension P4Lang.Parser: CompilableDeclaration {
public static func Compile( public static func Compile(
node: Node, withContext context: CompilerContext node: Node, withContext context: CompilerContext
) -> Result<(P4DataType, CompilerContext)?> { ) -> Result<(Declaration, CompilerContext)?> {
let parser_node = node let parser_node = node
#SkipUnlessNodeType<Node, (P4DataType, CompilerContext)?>( #SkipUnlessNodeType<Node, (P4DataType, CompilerContext)?>(
node: parser_node, type: "parserDeclaration") node: parser_node, type: "parserDeclaration")
@@ -369,13 +399,17 @@ extension P4Lang.Parser: CompilableDeclaration {
withContext: current_context) withContext: current_context)
{ {
case Result.Ok((let parser, let updated_context)): case Result.Ok((let parser, let updated_context)):
let parser_declaration = Declaration(
TypedIdentifier(id: parser.name, withType: P4Type(parser)))
// Create a new context with the name of the parser that was just compiled in scope. // Create a new context with the name of the parser that was just compiled in scope.
return .Ok( return .Ok(
( (
parser, parser_declaration,
context.update( context.extern_context
? context
: context.update(
newInstances: updated_context.instances.declare( newInstances: updated_context.instances.declare(
identifier: parser.name, withValue: P4Type(parser))) identifier: parser.name, withValue: parser_declaration.identifier.type))
)) ))
case Result.Error(let error): return .Error(error) case Result.Error(let error): return .Error(error)
} }
@@ -385,7 +419,7 @@ extension P4Lang.Parser: CompilableDeclaration {
extension Control: CompilableDeclaration { extension Control: CompilableDeclaration {
public static func Compile( public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(any Common.P4DataType, CompilerContext)?> { ) -> Common.Result<(Declaration, CompilerContext)?> {
#SkipUnlessNodeType<Node, (P4DataType, CompilerContext)?>( #SkipUnlessNodeType<Node, (P4DataType, CompilerContext)?>(
node: node, type: "control_declaration") node: node, type: "control_declaration")
@@ -516,19 +550,24 @@ extension Control: CompilableDeclaration {
} }
let declared_control = let declared_control =
(Control( Declaration(
TypedIdentifier(
id: control_name,
withType: P4Type(
Control(
named: control_name, withParameters: control_parameters, withTable: tables[0], named: control_name, withParameters: control_parameters, withTable: tables[0],
withActions: Actions(withActions: actions), withApply: apply) withActions: Actions(withActions: actions), withApply: apply))))
as P4DataType)
// Don't forget to add the newly declared Control to the instance that we were given // Don't forget to add the newly declared Control to the instance that we were given
// (and not the one that we entered to do the parsing of this Control). // (and not the one that we entered to do the parsing of this Control).
return .Ok( return .Ok(
( (
declared_control, declared_control,
context.update( context.extern_context
? context
: context.update(
newInstances: context.instances.declare( newInstances: context.instances.declare(
identifier: control_name, withValue: P4Type(declared_control))) identifier: control_name, withValue: declared_control.identifier.type))
)) ))
} }
} }
@@ -823,3 +862,54 @@ extension Table: Compilable {
(Table(withName: table_name, withPropertyList: table_property_list), current_context)) (Table(withName: table_name, withPropertyList: table_property_list), current_context))
} }
} }
extension ExternDeclaration: CompilableDeclaration {
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
) -> Common.Result<(Declaration, CompilerContext)?> {
let extern_declaration_node = node
#RequireNodeType<Node, (Declaration, CompilerContext)>(
node: extern_declaration_node, type: "extern_declaration",
nice_type_name: "Extern Declaration")
let declaration_node = extern_declaration_node.child(at: 1)!
#RequireNodeType<Node, (Declaration, CompilerContext)>(
node: declaration_node, type: "declaration", nice_type_name: "Declaration")
let declarationed_node = declaration_node.child(at: 0)!
let maybe_declared = Declaration.Compile(
node: declarationed_node, withContext: context.update(newExtern: true))
guard case .Ok(let maybe_declared) = maybe_declared else {
return .Error(maybe_declared.error()!)
}
guard case .some((let declared, _)) = maybe_declared else {
return .Ok(.none)
}
// Before we are okay with this declaration, it must already be registered as an extern
// with the matching "stuff".
let found_ffi = context.ffis.first { ffi in
ffi.type().dataType().eq(rhs: declared.identifier.type.dataType())
}
guard let found_ffi = found_ffi else {
return .Error(
ErrorOnNode(
node: declarationed_node,
withError:
"Could not find a foreign function that matches the extern declaration (\(declared))"))
}
let extern_declaration = Declaration(extern: declared, ffi: found_ffi)
return .Ok(
(
extern_declaration,
context.update(
newExterns: context.externs.declare(
identifier: declared.identifier, withValue: extern_declaration))
))
}
}
+47 -6
View File
@@ -670,6 +670,7 @@ extension FunctionCall: CompilableExpression {
static func compile( static func compile(
node: Node, withContext context: CompilerContext node: Node, withContext context: CompilerContext
) -> Result<EvaluatableExpression?> { ) -> Result<EvaluatableExpression?> {
let expression = node.child(at: 0)! let expression = node.child(at: 0)!
#SkipUnlessNodeType<Node, EvaluatableExpression?>( #SkipUnlessNodeType<Node, EvaluatableExpression?>(
node: expression, type: "function_call") node: expression, type: "function_call")
@@ -691,16 +692,33 @@ extension FunctionCall: CompilableExpression {
return Result.Error(maybe_callee_name.error()!) return Result.Error(maybe_callee_name.error()!)
} }
let maybe_callee = var maybe_callee: Result<(FunctionDeclaration?, Declaration?)> =
switch context.types.lookup(identifier: callee_name) { switch context.types.lookup(identifier: callee_name) {
case .Ok(let looked_up): case .Ok(let looked_up):
switch looked_up { switch looked_up {
case let callee as FunctionDeclaration: Result.Ok(callee) // What we found is actually a function declaration case let callee as FunctionDeclaration:
Result<(FunctionDeclaration?, Declaration?)>.Ok((callee, .none)) // What we found is actually a function declaration
default: default:
Result<FunctionDeclaration>.Error( Result<(FunctionDeclaration?, Declaration?)>.Error(
ErrorOnNode(node: currentChild!, withError: "\(callee_name) is not a function")) ErrorOnNode(node: currentChild!, withError: "\(callee_name) is not a function"))
} }
case .Error(let e): Result<FunctionDeclaration>.Error(e) case .Error(let e): Result<(FunctionDeclaration?, Declaration?)>.Error(e)
}
maybe_callee =
if case .Error(let e) = maybe_callee {
switch context.externs.lookup(identifier: callee_name) {
case .Ok(let callee as Declaration):
// Now, make sure that it is a function declaration!
switch callee.identifier.type.dataType() {
case is FunctionDeclaration: Result.Ok((.none, callee))
default:
.Error(ErrorOnNode(node: currentChild!, withError: "\(callee_name) is not a function"))
}
default: .Error(e)
}
} else {
maybe_callee
} }
guard case .Ok(let callee) = maybe_callee else { guard case .Ok(let callee) = maybe_callee else {
@@ -723,12 +741,35 @@ extension FunctionCall: CompilableExpression {
// Now, compare the arguments with the parameters: // Now, compare the arguments with the parameters:
if case .Error(let e) = arguments.compatible(callee.params) { let params =
switch callee {
case (.some(let callee), .none): Optional<ParameterList>.some(callee.params)
case (.none, .some(let callee)):
Optional<ParameterList>.some((callee.ffi!.type().dataType() as! FunctionDeclaration).params)
default: Optional<ParameterList>.none
}
guard case .some(let params) = params else {
return Result.Error(
ErrorOnNode(
node: node,
withError: "Could not lookup the parameters for the called function (\(callee_name))"))
}
if case .Error(let e) = arguments.compatible(params) {
return .Error(e) return .Error(e)
} }
// All good! // All good!
return .Ok(FunctionCall(callee, withArguments: arguments)) return switch callee {
case (.some(let callee), .none): .Ok(FunctionCall(callee, withArguments: arguments))
case (.none, .some(let callee)): .Ok(FunctionCall(callee.ffi!, withArguments: arguments))
default:
Result.Error(
ErrorOnNode(
node: node, withError: "Unexpected error occurred calling function named (\(callee_name))"
))
}
} }
} }
+15 -5
View File
@@ -24,18 +24,19 @@ import TreeSitterP4
public struct Program { public struct Program {
public static func Compile(_ source: String) -> Result<P4Lang.Program> { public static func Compile(_ source: String) -> Result<P4Lang.Program> {
return Program.Compile(source, withGlobalInstances: .none, withGlobalTypes: .none) return Program.Compile(source, withGlobalInstances: .none, withGlobalTypes: .none, withFFIs: [])
} }
public static func Compile( public static func Compile(
_ source: String, withGlobalInstances globalInstances: VarTypeScopes _ source: String, withGlobalInstances globalInstances: VarTypeScopes
) -> Result<P4Lang.Program> { ) -> Result<P4Lang.Program> {
return Program.Compile(source, withGlobalInstances: globalInstances, withGlobalTypes: .none) return Program.Compile(
source, withGlobalInstances: globalInstances, withGlobalTypes: .none, withFFIs: [])
} }
public static func Compile( public static func Compile(
_ source: String, withGlobalInstances globalInstances: VarTypeScopes?, _ source: String, withGlobalInstances globalInstances: VarTypeScopes?,
withGlobalTypes globalTypes: TypeTypeScopes? withGlobalTypes globalTypes: TypeTypeScopes?, withFFIs ffis: [P4FFI] = Array()
) -> Result<P4Lang.Program> { ) -> Result<P4Lang.Program> {
let maybe_parser = ConfigureP4Parser() let maybe_parser = ConfigureP4Parser()
@@ -54,8 +55,10 @@ public struct Program {
var program = P4Lang.Program() var program = P4Lang.Program()
// Set up a context for parsing. // Set up a context for parsing.
var compilation_context = CompilerContext( var compilation_context = CompilerContext()
withInstances: VarTypeScopes().enter(), withTypes: TypeTypeScopes().enter())
// Add our FFIs
compilation_context = compilation_context.update(newFFIs: ffis)
var errors: [Error] = Array() var errors: [Error] = Array()
@@ -120,6 +123,13 @@ public struct Program {
compilation_context.types.map { (_, v) in compilation_context.types.map { (_, v) in
v v
}) })
// Any of the extern types that are in the top-level scope should go into the program!
program.externs = Array(
compilation_context.externs.map { (_, v) in
v
})
return Result.Ok(program) return Result.Ok(program)
} }
} }
+5 -1
View File
@@ -39,9 +39,13 @@ public protocol CompilableType {
} }
public protocol CompilableDeclaration { public protocol CompilableDeclaration {
/// Info
///
/// Extensions should update the context with the newly declared item _unless_
/// they are in an extern context (``CompilerContext.extern_context``).
static func Compile( static func Compile(
node: Node, withContext context: CompilerContext node: Node, withContext context: CompilerContext
) -> Result<(P4DataType, CompilerContext)?> ) -> Result<(Declaration, CompilerContext)?>
} }
public protocol Compilable<T> { public protocol Compilable<T> {
-136
View File
@@ -1,136 +0,0 @@
// 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.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)"
}
/// Calculate whether the `argument` is compatible with this parameter.
public func compatible(_ argument: Argument) -> Bool {
let arg_type = argument.argument.type()
// If the parameter is (in)out, then the argument must be an lvalue.
if let param_direction = self.type.direction(),
param_direction == Direction.In || param_direction == Direction.InOut
{
if !(argument.argument is EvaluatableLValueExpression) {
return false
}
}
return arg_type.dataType().eq(rhs: self.type.dataType())
}
}
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 !param.compatible(arg) {
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
}
}
+43 -3
View File
@@ -17,7 +17,46 @@
import Common import Common
public struct Declaration {} public struct Declaration: P4DataType {
public let identifier: TypedIdentifier
public let extern: Bool
public let ffi: P4FFI?
public init(_ id: TypedIdentifier) {
identifier = id
ffi = .none
self.extern = false
}
public init(extern: Declaration, ffi: P4FFI) {
identifier = extern.identifier
self.ffi = ffi
self.extern = true
}
public func eq(rhs: any Common.P4DataType) -> Bool {
return switch rhs {
case let rrhs as Declaration:
self.identifier.type.dataType().eq(rhs: rrhs.identifier.type.dataType())
&& self.extern == rrhs.extern
default: false
}
}
public func def() -> any Common.P4DataValue {
// TODO: Is a default of the extern'd type the right way to go?
return self.identifier.type.dataType().def()
}
public func type() -> any Common.P4DataType {
return self
}
public var description: String {
return "Extern \(self.identifier)"
}
}
public struct ExternDeclaration {}
public struct FunctionDeclaration: P4DataType, P4DataValue { public struct FunctionDeclaration: P4DataType, P4DataValue {
public func type() -> any Common.P4DataType { public func type() -> any Common.P4DataType {
@@ -25,6 +64,7 @@ public struct FunctionDeclaration: P4DataType, P4DataValue {
} }
public func eq(rhs: any Common.P4DataType) -> Bool { public func eq(rhs: any Common.P4DataType) -> Bool {
print("Checking a type: me: \(self) vs them: \(rhs)!")
switch rhs { switch rhs {
case let frhs as FunctionDeclaration: case let frhs as FunctionDeclaration:
return frhs.tipe.eq(self.tipe) && frhs.params == self.params return frhs.tipe.eq(self.tipe) && frhs.params == self.params
@@ -79,14 +119,14 @@ public struct FunctionDeclaration: P4DataType, P4DataValue {
return "Function named \(self.name) that returns \(self.tipe) with parameters \(self.params)" return "Function named \(self.name) that returns \(self.tipe) with parameters \(self.params)"
} }
public var body: EvaluatableStatement? public var body: BlockStatement?
public var params: ParameterList public var params: ParameterList
public var name: Identifier public var name: Identifier
public var tipe: P4Type public var tipe: P4Type
public init( public init(
named name: Identifier, ofType type: P4Type, withParameters parameters: ParameterList, named name: Identifier, ofType type: P4Type, withParameters parameters: ParameterList,
withBody body: EvaluatableStatement? withBody body: BlockStatement?
) { ) {
self.name = name self.name = name
self.tipe = type self.tipe = type
+11 -2
View File
@@ -128,11 +128,20 @@ public struct FieldAccessExpression {
} }
public struct FunctionCall { public struct FunctionCall {
public let callee: FunctionDeclaration public let callee: (FunctionDeclaration?, P4FFI?)
public let arguments: ArgumentList public let arguments: ArgumentList
public let return_type: P4DataType
public init(_ callee: FunctionDeclaration, withArguments arguments: ArgumentList) { public init(_ callee: FunctionDeclaration, withArguments arguments: ArgumentList) {
self.callee = callee self.callee = (callee, .none)
self.arguments = arguments self.arguments = arguments
self.return_type = callee.tipe.dataType()
}
public init(_ callee: P4FFI, withArguments arguments: ArgumentList) {
self.callee = (.none, callee)
self.arguments = arguments
// ASSUME: That the FFI has been checked and the type is always a function declaration.
self.return_type = (callee.type().dataType() as! FunctionDeclaration).tipe.dataType()
} }
} }
+18
View File
@@ -0,0 +1,18 @@
// 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
+28 -4
View File
@@ -27,12 +27,13 @@ public struct ExpressionStatement {
public struct Program { public struct Program {
public var types: [P4DataType] = Array() public var types: [P4DataType] = Array()
public var externs: [P4DataType] = Array()
public var instances: [P4Type] = Array() public var instances: [P4Type] = Array()
/// Type of closure for filtering results from ``Program/InstancesWithTypes(_:)`` /// Type of closure for filtering results from ``Program/InstancesWithTypes(_:)``
public typealias AttributedTypeFilter = (P4Type) -> Bool public typealias TypeFilter = (P4Type) -> Bool
/// Type of closure for filtering results from ``Program/TypesWithTypes(_:)`` /// Type of closure for filtering results from ``Program/TypesWithTypes(_:)``
public typealias TypeFilter = (P4DataType) -> Bool public typealias DataTypeFilter = (P4DataType) -> Bool
/// Retrieve global instances in the compiled P4 program. /// Retrieve global instances in the compiled P4 program.
public func InstancesWithTypes() -> [P4Type] { public func InstancesWithTypes() -> [P4Type] {
@@ -51,7 +52,7 @@ public struct Program {
/// ///
/// @Snippet(path: "use-program-instanceswithtypes", slice: "include") /// @Snippet(path: "use-program-instanceswithtypes", slice: "include")
/// ///
public func InstancesWithTypes(_ filter: AttributedTypeFilter) -> [P4Type] { public func InstancesWithTypes(_ filter: TypeFilter) -> [P4Type] {
return self.instances.filter { instance in return self.instances.filter { instance in
filter(instance) filter(instance)
} }
@@ -74,12 +75,35 @@ public struct Program {
/// ///
/// @Snippet(path: "use-program-typeswithtypes", slice: "include") /// @Snippet(path: "use-program-typeswithtypes", slice: "include")
/// ///
public func TypesWithTypes(_ filter: TypeFilter) -> [P4DataType] { public func TypesWithTypes(_ filter: DataTypeFilter) -> [P4DataType] {
return self.types.filter { instance in return self.types.filter { instance in
filter(instance) filter(instance)
} }
} }
/// Retrieve extern types in the compiled P4 program.
public func Externs() -> [P4DataType] {
return self.externs
}
/// Retrieve extern types declared in the compiled P4 program.
///
/// Use the given filter to select which of the extern types
/// declared in the compiled P4 program to retrieve.
///
/// If the compiled P4 program (from the source in the
/// string `p4_program_with_struct_decl`) has two extern structs declared and
/// you only want to select the one named `agg`, you could
/// use a filter like
///
/// @Snippet(path: "use-program-typeswithtypes", slice: "include")
///
public func Externs(_ filter: DataTypeFilter) -> [P4DataType] {
return self.externs.filter { instance in
filter(instance)
}
}
/// Find the program's main parser /// Find the program's main parser
/// ///
/// Note: For now, the main parser is expected to be named main_parser. /// Note: For now, the main parser is expected to be named main_parser.
+30 -8
View File
@@ -477,33 +477,55 @@ extension FunctionCall: EvaluatableExpression {
execution: Common.ProgramExecution execution: Common.ProgramExecution
) -> (Common.Result<P4Value>, ProgramExecution) { ) -> (Common.Result<P4Value>, ProgramExecution) {
guard let body = self.callee.body else { let body_params:
return ( Result<((ProgramExecution) -> (ControlFlow, ProgramExecution), ParameterList)> =
.Error(Error(withMessage: "No body for called function (\(self.callee.name))")), execution switch self.callee {
) case (.some(let callee), .none):
switch callee.body {
case .some(let body):
.Ok(
(
{ (execution: ProgramExecution) -> (ControlFlow, ProgramExecution) in
return body.evaluate(execution: execution)
}, callee.params
))
case .none: .Error(Error(withMessage: "No body for called function (\(callee.name))"))
}
case (.none, .some(let callee)):
.Ok(
(
{ (execution: ProgramExecution) -> (ControlFlow, ProgramExecution) in
return callee.execute(execution: execution)
}, callee.parameters()
))
default: .Error(Error(withMessage: "No callee found for function call"))
}
guard case .Ok(let body) = body_params else {
return (.Error(body_params.error()!), execution)
} }
let call_body: (ProgramExecution) -> (Result<P4Value>, ProgramExecution) = { let call_body: (ProgramExecution) -> (Result<P4Value>, ProgramExecution) = {
(execution: ProgramExecution) in (execution: ProgramExecution) in
let (control_flow, updated_execution) = body.evaluate(execution: execution) let (control_flow, updated_execution) = body.0(execution)
return switch control_flow { return switch control_flow {
case ControlFlow.Return(.some(let value)): (.Ok(value), updated_execution) case ControlFlow.Return(.some(let value)): (.Ok(value), updated_execution)
default: default:
( (
.Error( .Error(
Error(withMessage: "No value returned from called function (\(self.callee.name))")), Error(withMessage: "No value returned from called function")),
execution execution
) )
} }
} }
return Call( return Call(
body: call_body, withArguments: self.arguments, withParameters: self.callee.params, body: call_body, withArguments: self.arguments, withParameters: body.1,
inExecution: execution) inExecution: execution)
} }
public func type() -> P4Type { public func type() -> P4Type {
return self.callee.tipe return P4Type(self.return_type)
} }
} }
+5 -12
View File
@@ -45,11 +45,8 @@ extension ParserStateDirectTransition: EvaluatableParserState {
) -> (any EvaluatableParserState, Common.ProgramExecution) { ) -> (any EvaluatableParserState, Common.ProgramExecution) {
var program = program.enter_scope() var program = program.enter_scope()
let (control_flow, next_execution) = program.evaluator.ExecuteStatement( let (control_flow, next_execution) = program.evaluator.ExecuteStatements(
statements, statements, inExecution: program)
handleResult: { (control_flow, execution) in
return (control_flow, execution)
}, inExecution: program)
switch control_flow { switch control_flow {
case .Next: program = next_execution case .Next: program = next_execution
@@ -106,12 +103,8 @@ extension ParserStateSelectTransition: EvaluatableParserState {
) -> (any EvaluatableParserState, Common.ProgramExecution) { ) -> (any EvaluatableParserState, Common.ProgramExecution) {
var program = program.enter_scope() var program = program.enter_scope()
let (control_flow, next_execution) = program.evaluator.ExecuteStatement( let (control_flow, next_execution) = program.evaluator.ExecuteStatements(
statements, statements, inExecution: program)
handleResult: { (control_flow, execution) in
return (control_flow, execution)
}, inExecution: program)
switch control_flow { switch control_flow {
case .Next: program = next_execution case .Next: program = next_execution
case .Error: return (reject, next_execution.exit_scope()) case .Error: return (reject, next_execution.exit_scope())
@@ -151,7 +144,7 @@ extension ParserStateSelectTransition: EvaluatableParserState {
extension Parser: LibraryCallable { extension Parser: LibraryCallable {
public typealias T = InstantiatedParserState public typealias T = InstantiatedParserState
public func call( public func call(
execution: Common.ProgramExecution, arguments: P4Lang.ArgumentList execution: Common.ProgramExecution, arguments: ArgumentList
) -> (P4Lang.InstantiatedParserState, Common.ProgramExecution) { ) -> (P4Lang.InstantiatedParserState, Common.ProgramExecution) {
var execution = execution.enter_scope() var execution = execution.enter_scope()
+2 -2
View File
@@ -176,7 +176,7 @@ import TreeSitterP4
#RequireErrorResult( #RequireErrorResult(
Error( Error(
withMessage: withMessage:
"{56, 21}: Failed to parse a statement element: {63, 9}: Failed to parse a statement element: {63, 1}: Cannot assign value with type Boolean to identifier x that is in parameter" "{63, 9}: Failed to parse a statement element: {63, 1}: Cannot assign value with type Boolean to identifier x that is in parameter"
), ),
Program.Compile(simple_parser_declaration)) Program.Compile(simple_parser_declaration))
) )
@@ -196,7 +196,7 @@ import TreeSitterP4
#RequireErrorResult( #RequireErrorResult(
Error( Error(
withMessage: withMessage:
"{113, 27}: Failed to parse a statement element: {120, 15}: Failed to parse a statement element: {120, 7}: Cannot assign to field yesno of x that is in parameter" "{120, 15}: Failed to parse a statement element: {120, 7}: Cannot assign to field yesno of x that is in parameter"
), ),
Program.Compile(simple_parser_declaration)) Program.Compile(simple_parser_declaration))
) )
+124
View File
@@ -0,0 +1,124 @@
// 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
public struct Return5: P4FFI {
public func execute(execution: Common.ProgramExecution) -> (
Common.ControlFlow, Common.ProgramExecution
) {
return (ControlFlow.Return(P4Value(P4IntValue(withValue: 5))), execution)
}
public func parameters() -> ParameterList {
return ParameterList()
}
public func type() -> Common.P4Type {
return P4Type(
FunctionDeclaration(
named: Identifier(name: "externally"), ofType: P4Type(P4Int()),
withParameters: ParameterList(), withBody: .none?))
}
public init() {}
}
public struct Return6: P4FFI {
public func execute(execution: Common.ProgramExecution) -> (
Common.ControlFlow, Common.ProgramExecution
) {
return (ControlFlow.Return(P4Value(P4IntValue(withValue: 6))), execution)
}
public func parameters() -> ParameterList {
return ParameterList()
}
public func type() -> Common.P4Type {
return P4Type(
FunctionDeclaration(
named: Identifier(name: "externally"), ofType: P4Type(P4Int()),
withParameters: ParameterList(), withBody: .none?))
}
public init() {}
}
@Test func test_extern_function_declaration() async throws {
let simple_parser_declaration = """
extern int externally();
parser main_parser() {
state start {
int t = externally();
transition select (t == 5) {
true: accept;
false: reject;
};
}
};
"""
let externally = Return5()
let program = try! #UseOkResult(
Program.Compile(
simple_parser_declaration, withGlobalInstances: .none, withGlobalTypes: .none,
withFFIs: [externally]))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.accept)
}
@Test func test_extern_function_declaration2() async throws {
let simple_parser_declaration = """
extern int externally();
parser main_parser() {
state start {
int t = externally();
transition select (t == 5) {
true: accept;
false: reject;
};
}
};
"""
let externally = Return6()
let program = try! #UseOkResult(
Program.Compile(
simple_parser_declaration, withGlobalInstances: .none, withGlobalTypes: .none,
withFFIs: [externally]))
let runtime = try #UseOkResult(P4Runtime.ParserRuntime.create(program: program))
let (state_result, _) = try! #UseOkResult(runtime.run())
#expect(AsInstantiatedParserState(state_result) == P4Lang.reject)
}
+1 -1
View File
@@ -98,7 +98,7 @@ import P4Lang
#RequireErrorResult<(EvaluatableStatement, CompilerContext)>( #RequireErrorResult<(EvaluatableStatement, CompilerContext)>(
Error(withMessage: "{2, 154}: Did not find assignment statement"), Error(withMessage: "{2, 154}: Did not find assignment statement"),
ParserAssignmentStatement.Compile( // Note: Calling ParserAssignmentStatement compilation directly. ParserAssignmentStatement.Compile( // Note: Calling ParserAssignmentStatement compilation directly.
node: result.rootNode!, withContext: CompilerContext(withInstances: VarTypeScopes())))) node: result.rootNode!, withContext: CompilerContext())))
} }
@Test func test_simple_compiler_parser_with_parameters() async throws { @Test func test_simple_compiler_parser_with_parameters() async throws {