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 {