diff --git a/Sources/Common/Call.swift b/Sources/Common/Call.swift
new file mode 100644
index 0000000..f365200
--- /dev/null
+++ b/Sources/Common/Call.swift
@@ -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 .
+
+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
+ }
+}
diff --git a/Sources/Common/FFI.swift b/Sources/Common/FFI.swift
new file mode 100644
index 0000000..821c780
--- /dev/null
+++ b/Sources/Common/FFI.swift
@@ -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 .
+
+public protocol P4FFI {
+ func execute(execution: ProgramExecution) -> (ControlFlow, ProgramExecution)
+ func type() -> P4Type
+ func parameters() -> ParameterList
+}
diff --git a/Sources/P4Compiler/Compiler.swift b/Sources/P4Compiler/Compiler.swift
index 3b874f8..8afaf30 100644
--- a/Sources/P4Compiler/Compiler.swift
+++ b/Sources/P4Compiler/Compiler.swift
@@ -52,60 +52,111 @@ public func ErrorOnNode(node: Node, withError error: String) -> Error {
public struct CompilerContext {
let instances: VarTypeScopes
let types: TypeTypeScopes
+ let externs: TypeTypeScopes
+ let ffis: [P4FFI]
let expected_type: P4Type?
+ let extern_context: Bool
- public init(withInstances _instances: VarTypeScopes) {
- instances = _instances
- types = TypeTypeScopes()
+ public init() {
+ instances = VarTypeScopes().enter()
+ types = TypeTypeScopes().enter()
+ externs = TypeTypeScopes().enter()
expected_type = .none
+ extern_context = false
+ ffis = Array()
}
public init(withInstances _instances: VarTypeScopes, withTypes _types: TypeTypeScopes) {
instances = _instances
types = _types
+ externs = TypeTypeScopes().enter()
expected_type = .none
+ extern_context = false
+ ffis = Array()
}
public init(
withInstances _instances: VarTypeScopes, withTypes _types: TypeTypeScopes,
- withExpectation expectation: P4Type?
+ withExpectation expectation: P4Type?, withExtern extern: Bool,
+ withExterns externs: TypeTypeScopes, withFFIs foreigns: [P4FFI]
) {
instances = _instances
types = _types
expected_type = expectation
+ extern_context = extern
+ self.externs = externs
+ ffis = foreigns
}
/// 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.
- /// - 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 {
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
///
- /// 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.
- /// - 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 {
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
///
- /// 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.
- /// - 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 {
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)
+ }
}
diff --git a/Sources/P4Compiler/Declarations.swift b/Sources/P4Compiler/Declarations.swift
index 5a9eb8e..2243836 100644
--- a/Sources/P4Compiler/Declarations.swift
+++ b/Sources/P4Compiler/Declarations.swift
@@ -25,12 +25,13 @@ import TreeSitterP4
extension Declaration: CompilableDeclaration {
public static func Compile(
node: Node, withContext context: CompilerContext
- ) -> Result<(P4DataType, CompilerContext)?> {
+ ) -> Result<(Declaration, CompilerContext)?> {
let declaration_compilers: [String: CompilableDeclaration.Type] = [
"function_declaration": FunctionDeclaration.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 {
@@ -44,7 +45,7 @@ extension Declaration: CompilableDeclaration {
extension FunctionDeclaration: CompilableDeclaration {
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
- ) -> Common.Result<(any Common.P4DataType, CompilerContext)?> {
+ ) -> Common.Result<(Declaration, CompilerContext)?> {
let function_declaration_node = node
#RequireNodeType(
node: function_declaration_node, type: "function_declaration",
@@ -96,52 +97,69 @@ extension FunctionDeclaration: CompilableDeclaration {
}
context = updated_context
+ var function_body: BlockStatement? = .none
+
currentChildIdx += 1
currentChildIdxSafe += 1
- if function_declaration_node.childCount < currentChildIdxSafe {
- return Result.Error(
- ErrorOnNode(
- node: function_declaration_node, withError: "Missing function declaration component"))
- }
- currentChild = function_declaration_node.child(at: currentChildIdx)
+ if currentChildIdxSafe <= function_declaration_node.childCount {
+ currentChild = function_declaration_node.child(at: currentChildIdx)
- // Add the parameters into scope.
- var function_scope = context.instances.enter()
- for parameter in function_parameters.parameters {
- function_scope = function_scope.declare(
- identifier: parameter.name, withValue: parameter.type)
+ // Add the parameters into scope.
+ var function_scope = context.instances.enter()
+ for parameter in function_parameters.parameters {
+ function_scope = function_scope.declare(
+ identifier: parameter.name, withValue: parameter.type)
+ }
+
+ let maybe_function_body = BlockStatement.Compile(
+ node: currentChild!,
+ withContext: context.update(newInstances: function_scope).update(
+ newExpectation: function_type))
+
+ guard case .Ok((let parsed_function_body, _)) = maybe_function_body else {
+ return .Error(maybe_function_body.error()!)
+ }
+ function_body = (parsed_function_body as! BlockStatement)
+ } else {
+
+ // 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 maybe_function_body = Parser.Statement.Compile(
- node: currentChild!,
- withContext: context.update(newInstances: function_scope).update(
- newExpectation: function_type))
- guard case .Ok((let function_body, _)) = maybe_function_body else {
- return .Error(maybe_function_body.error()!)
- }
-
- let function_declaration = FunctionDeclaration(
- named: function_name, ofType: function_type, withParameters: function_parameters,
- withBody: function_body)
+ let function_declaration = Declaration(
+ TypedIdentifier(
+ id: function_name,
+ withType: P4Type(
+ FunctionDeclaration(
+ named: function_name, ofType: function_type, withParameters: function_parameters,
+ withBody: function_body))))
// Do not use the updated context returned by parsing the body
// and do not use the function_scope, either.
+ // And, do not update the context if we are compiling in an
+ // extern context.
return .Ok(
(
function_declaration,
- context.update(
- newTypes: context.types.declare(
- identifier: function_name, withValue: function_declaration))
+ context.extern_context
+ ? context
+ : context.update(
+ newTypes: context.types.declare(
+ identifier: function_name, withValue: function_declaration.identifier.type.dataType())
+ )
))
}
}
-struct StructDeclaration {}
-
-extension StructDeclaration: CompilableDeclaration {
- static func Compile(
+extension P4Struct: CompilableDeclaration {
+ static public func Compile(
node: Node, withContext context: CompilerContext
- ) -> Result<(P4DataType, CompilerContext)?> {
+ ) -> Result<(Declaration, CompilerContext)?> {
let struct_declaration_node = node.child(at: 0)!
var currentChildIdx = 0
@@ -195,12 +213,18 @@ extension StructDeclaration: CompilableDeclaration {
// If there are no fields, it will be a "}"
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(
(
struc,
- context.update(
- newTypes: context.types.declare(identifier: struct_identifier, withValue: struc))
+ context.extern_context
+ ? context
+ : context.update(
+ newTypes: context.types.declare(
+ identifier: struct_identifier, withValue: struc.identifier.type.dataType()))
))
}
@@ -234,14 +258,20 @@ extension StructDeclaration: CompilableDeclaration {
}.joined(separator: ";"))))
}
- let declared_struct = P4Struct(
- withName: struct_identifier, andFields: P4StructFields(parsed_fields))
+ let declared_struct = Declaration(
+ TypedIdentifier(
+ id: struct_identifier,
+ withType: P4Type(
+ P4Struct(
+ withName: struct_identifier, andFields: P4StructFields(parsed_fields)))))
return .Ok(
(
declared_struct,
- current_context.update(
- newTypes: current_context.types.declare(
- identifier: struct_identifier, withValue: declared_struct))
+ current_context.extern_context
+ ? current_context
+ : current_context.update(
+ newTypes: current_context.types.declare(
+ identifier: struct_identifier, withValue: declared_struct.identifier.type.dataType()))
))
}
}
@@ -249,7 +279,7 @@ extension StructDeclaration: CompilableDeclaration {
extension P4Lang.Parser: CompilableDeclaration {
public static func Compile(
node: Node, withContext context: CompilerContext
- ) -> Result<(P4DataType, CompilerContext)?> {
+ ) -> Result<(Declaration, CompilerContext)?> {
let parser_node = node
#SkipUnlessNodeType(
node: parser_node, type: "parserDeclaration")
@@ -369,13 +399,17 @@ extension P4Lang.Parser: CompilableDeclaration {
withContext: current_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.
return .Ok(
(
- parser,
- context.update(
- newInstances: updated_context.instances.declare(
- identifier: parser.name, withValue: P4Type(parser)))
+ parser_declaration,
+ context.extern_context
+ ? context
+ : context.update(
+ newInstances: updated_context.instances.declare(
+ identifier: parser.name, withValue: parser_declaration.identifier.type))
))
case Result.Error(let error): return .Error(error)
}
@@ -385,7 +419,7 @@ extension P4Lang.Parser: CompilableDeclaration {
extension Control: CompilableDeclaration {
public static func Compile(
node: SwiftTreeSitter.Node, withContext context: CompilerContext
- ) -> Common.Result<(any Common.P4DataType, CompilerContext)?> {
+ ) -> Common.Result<(Declaration, CompilerContext)?> {
#SkipUnlessNodeType(
node: node, type: "control_declaration")
@@ -516,19 +550,24 @@ extension Control: CompilableDeclaration {
}
let declared_control =
- (Control(
- named: control_name, withParameters: control_parameters, withTable: tables[0],
- withActions: Actions(withActions: actions), withApply: apply)
- as P4DataType)
+ Declaration(
+ TypedIdentifier(
+ id: control_name,
+ withType: P4Type(
+ Control(
+ named: control_name, withParameters: control_parameters, withTable: tables[0],
+ withActions: Actions(withActions: actions), withApply: apply))))
// 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).
return .Ok(
(
declared_control,
- context.update(
- newInstances: context.instances.declare(
- identifier: control_name, withValue: P4Type(declared_control)))
+ context.extern_context
+ ? context
+ : context.update(
+ newInstances: context.instances.declare(
+ 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))
}
}
+
+extension ExternDeclaration: CompilableDeclaration {
+ public static func Compile(
+ node: SwiftTreeSitter.Node, withContext context: CompilerContext
+ ) -> Common.Result<(Declaration, CompilerContext)?> {
+ let extern_declaration_node = node
+ #RequireNodeType(
+ node: extern_declaration_node, type: "extern_declaration",
+ nice_type_name: "Extern Declaration")
+ let declaration_node = extern_declaration_node.child(at: 1)!
+ #RequireNodeType(
+ 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))
+ ))
+ }
+}
diff --git a/Sources/P4Compiler/Expression.swift b/Sources/P4Compiler/Expression.swift
index 5a5bb32..b2816b3 100644
--- a/Sources/P4Compiler/Expression.swift
+++ b/Sources/P4Compiler/Expression.swift
@@ -670,6 +670,7 @@ extension FunctionCall: CompilableExpression {
static func compile(
node: Node, withContext context: CompilerContext
) -> Result {
+
let expression = node.child(at: 0)!
#SkipUnlessNodeType(
node: expression, type: "function_call")
@@ -691,16 +692,33 @@ extension FunctionCall: CompilableExpression {
return Result.Error(maybe_callee_name.error()!)
}
- let maybe_callee =
+ var maybe_callee: Result<(FunctionDeclaration?, Declaration?)> =
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
+ case let callee as FunctionDeclaration:
+ Result<(FunctionDeclaration?, Declaration?)>.Ok((callee, .none)) // What we found is actually a function declaration
default:
- Result.Error(
+ Result<(FunctionDeclaration?, Declaration?)>.Error(
ErrorOnNode(node: currentChild!, withError: "\(callee_name) is not a function"))
}
- case .Error(let e): Result.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 {
@@ -723,12 +741,35 @@ extension FunctionCall: CompilableExpression {
// 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.some(callee.params)
+ case (.none, .some(let callee)):
+ Optional.some((callee.ffi!.type().dataType() as! FunctionDeclaration).params)
+ default: Optional.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)
}
// 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))"
+ ))
+ }
}
}
diff --git a/Sources/P4Compiler/Program.swift b/Sources/P4Compiler/Program.swift
index 7327fbc..be34b95 100644
--- a/Sources/P4Compiler/Program.swift
+++ b/Sources/P4Compiler/Program.swift
@@ -24,18 +24,19 @@ import TreeSitterP4
public struct Program {
public static func Compile(_ source: String) -> Result {
- return Program.Compile(source, withGlobalInstances: .none, withGlobalTypes: .none)
+ return Program.Compile(source, withGlobalInstances: .none, withGlobalTypes: .none, withFFIs: [])
}
public static func Compile(
_ source: String, withGlobalInstances globalInstances: VarTypeScopes
) -> Result {
- return Program.Compile(source, withGlobalInstances: globalInstances, withGlobalTypes: .none)
+ return Program.Compile(
+ source, withGlobalInstances: globalInstances, withGlobalTypes: .none, withFFIs: [])
}
public static func Compile(
_ source: String, withGlobalInstances globalInstances: VarTypeScopes?,
- withGlobalTypes globalTypes: TypeTypeScopes?
+ withGlobalTypes globalTypes: TypeTypeScopes?, withFFIs ffis: [P4FFI] = Array()
) -> Result {
let maybe_parser = ConfigureP4Parser()
@@ -54,8 +55,10 @@ public struct Program {
var program = P4Lang.Program()
// Set up a context for parsing.
- var compilation_context = CompilerContext(
- withInstances: VarTypeScopes().enter(), withTypes: TypeTypeScopes().enter())
+ var compilation_context = CompilerContext()
+
+ // Add our FFIs
+ compilation_context = compilation_context.update(newFFIs: ffis)
var errors: [Error] = Array()
@@ -120,6 +123,13 @@ public struct Program {
compilation_context.types.map { (_, v) in
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)
}
}
diff --git a/Sources/P4Compiler/Protocols.swift b/Sources/P4Compiler/Protocols.swift
index 5f5cc24..cd0711f 100644
--- a/Sources/P4Compiler/Protocols.swift
+++ b/Sources/P4Compiler/Protocols.swift
@@ -39,9 +39,13 @@ public protocol CompilableType {
}
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(
node: Node, withContext context: CompilerContext
- ) -> Result<(P4DataType, CompilerContext)?>
+ ) -> Result<(Declaration, CompilerContext)?>
}
public protocol Compilable {
diff --git a/Sources/P4Lang/Common.swift b/Sources/P4Lang/Common.swift
index 3b785c2..e69de29 100644
--- a/Sources/P4Lang/Common.swift
+++ b/Sources/P4Lang/Common.swift
@@ -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 .
-
-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
- }
-}
diff --git a/Sources/P4Lang/Declarations.swift b/Sources/P4Lang/Declarations.swift
index 6b735dd..6a923c1 100644
--- a/Sources/P4Lang/Declarations.swift
+++ b/Sources/P4Lang/Declarations.swift
@@ -17,7 +17,46 @@
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 func type() -> any Common.P4DataType {
@@ -25,6 +64,7 @@ public struct FunctionDeclaration: P4DataType, P4DataValue {
}
public func eq(rhs: any Common.P4DataType) -> Bool {
+ print("Checking a type: me: \(self) vs them: \(rhs)!")
switch rhs {
case let frhs as FunctionDeclaration:
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)"
}
- public var body: EvaluatableStatement?
+ public var body: BlockStatement?
public var params: ParameterList
public var name: Identifier
public var tipe: P4Type
public init(
named name: Identifier, ofType type: P4Type, withParameters parameters: ParameterList,
- withBody body: EvaluatableStatement?
+ withBody body: BlockStatement?
) {
self.name = name
self.tipe = type
diff --git a/Sources/P4Lang/Expressions.swift b/Sources/P4Lang/Expressions.swift
index ec198db..90eaf37 100644
--- a/Sources/P4Lang/Expressions.swift
+++ b/Sources/P4Lang/Expressions.swift
@@ -128,11 +128,20 @@ public struct FieldAccessExpression {
}
public struct FunctionCall {
- public let callee: FunctionDeclaration
+ public let callee: (FunctionDeclaration?, P4FFI?)
public let arguments: ArgumentList
+ public let return_type: P4DataType
public init(_ callee: FunctionDeclaration, withArguments arguments: ArgumentList) {
- self.callee = callee
+ self.callee = (callee, .none)
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()
}
}
diff --git a/Sources/P4Lang/FFI.swift b/Sources/P4Lang/FFI.swift
new file mode 100644
index 0000000..37b6318
--- /dev/null
+++ b/Sources/P4Lang/FFI.swift
@@ -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 .
+
+import Common
diff --git a/Sources/P4Lang/Program.swift b/Sources/P4Lang/Program.swift
index 504f1f0..aaa4072 100644
--- a/Sources/P4Lang/Program.swift
+++ b/Sources/P4Lang/Program.swift
@@ -27,12 +27,13 @@ public struct ExpressionStatement {
public struct Program {
public var types: [P4DataType] = Array()
+ public var externs: [P4DataType] = Array()
public var instances: [P4Type] = Array()
/// 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(_:)``
- public typealias TypeFilter = (P4DataType) -> Bool
+ public typealias DataTypeFilter = (P4DataType) -> Bool
/// Retrieve global instances in the compiled P4 program.
public func InstancesWithTypes() -> [P4Type] {
@@ -51,7 +52,7 @@ public struct Program {
///
/// @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
filter(instance)
}
@@ -74,12 +75,35 @@ public struct Program {
///
/// @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
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
///
/// Note: For now, the main parser is expected to be named main_parser.
diff --git a/Sources/P4Runtime/Expressions.swift b/Sources/P4Runtime/Expressions.swift
index 18e7015..7d517ce 100644
--- a/Sources/P4Runtime/Expressions.swift
+++ b/Sources/P4Runtime/Expressions.swift
@@ -477,33 +477,55 @@ extension FunctionCall: EvaluatableExpression {
execution: Common.ProgramExecution
) -> (Common.Result, ProgramExecution) {
- guard let body = self.callee.body else {
- return (
- .Error(Error(withMessage: "No body for called function (\(self.callee.name))")), execution
- )
+ let body_params:
+ Result<((ProgramExecution) -> (ControlFlow, ProgramExecution), ParameterList)> =
+ 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, ProgramExecution) = {
(execution: ProgramExecution) in
- let (control_flow, updated_execution) = body.evaluate(execution: execution)
+ let (control_flow, updated_execution) = body.0(execution)
return switch control_flow {
case ControlFlow.Return(.some(let value)): (.Ok(value), updated_execution)
default:
(
.Error(
- Error(withMessage: "No value returned from called function (\(self.callee.name))")),
+ Error(withMessage: "No value returned from called function")),
execution
)
}
}
return Call(
- body: call_body, withArguments: self.arguments, withParameters: self.callee.params,
+ body: call_body, withArguments: self.arguments, withParameters: body.1,
inExecution: execution)
}
public func type() -> P4Type {
- return self.callee.tipe
+ return P4Type(self.return_type)
}
}
diff --git a/Sources/P4Runtime/Parser.swift b/Sources/P4Runtime/Parser.swift
index ad04782..281bd0f 100644
--- a/Sources/P4Runtime/Parser.swift
+++ b/Sources/P4Runtime/Parser.swift
@@ -45,11 +45,8 @@ extension ParserStateDirectTransition: EvaluatableParserState {
) -> (any EvaluatableParserState, Common.ProgramExecution) {
var program = program.enter_scope()
- let (control_flow, next_execution) = program.evaluator.ExecuteStatement(
- statements,
- handleResult: { (control_flow, execution) in
- return (control_flow, execution)
- }, inExecution: program)
+ let (control_flow, next_execution) = program.evaluator.ExecuteStatements(
+ statements, inExecution: program)
switch control_flow {
case .Next: program = next_execution
@@ -106,12 +103,8 @@ extension ParserStateSelectTransition: EvaluatableParserState {
) -> (any EvaluatableParserState, Common.ProgramExecution) {
var program = program.enter_scope()
- let (control_flow, next_execution) = program.evaluator.ExecuteStatement(
- statements,
- handleResult: { (control_flow, execution) in
- return (control_flow, execution)
- }, inExecution: program)
-
+ let (control_flow, next_execution) = program.evaluator.ExecuteStatements(
+ statements, inExecution: program)
switch control_flow {
case .Next: program = next_execution
case .Error: return (reject, next_execution.exit_scope())
@@ -151,7 +144,7 @@ extension ParserStateSelectTransition: EvaluatableParserState {
extension Parser: LibraryCallable {
public typealias T = InstantiatedParserState
public func call(
- execution: Common.ProgramExecution, arguments: P4Lang.ArgumentList
+ execution: Common.ProgramExecution, arguments: ArgumentList
) -> (P4Lang.InstantiatedParserState, Common.ProgramExecution) {
var execution = execution.enter_scope()
diff --git a/Tests/p4rseTests/Declarations.swift b/Tests/p4rseTests/Declarations.swift
index f99936e..9f27284 100644
--- a/Tests/p4rseTests/Declarations.swift
+++ b/Tests/p4rseTests/Declarations.swift
@@ -176,7 +176,7 @@ import TreeSitterP4
#RequireErrorResult(
Error(
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))
)
@@ -196,7 +196,7 @@ import TreeSitterP4
#RequireErrorResult(
Error(
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))
)
diff --git a/Tests/p4rseTests/ExternDeclarations.swift b/Tests/p4rseTests/ExternDeclarations.swift
new file mode 100644
index 0000000..84706e9
--- /dev/null
+++ b/Tests/p4rseTests/ExternDeclarations.swift
@@ -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 .
+
+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)
+}
diff --git a/Tests/p4rseTests/ParserCompilerTests.swift b/Tests/p4rseTests/ParserCompilerTests.swift
index a413886..afbc00e 100644
--- a/Tests/p4rseTests/ParserCompilerTests.swift
+++ b/Tests/p4rseTests/ParserCompilerTests.swift
@@ -98,7 +98,7 @@ import P4Lang
#RequireErrorResult<(EvaluatableStatement, CompilerContext)>(
Error(withMessage: "{2, 154}: Did not find assignment statement"),
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 {