├── .serena ├── .gitignore ├── .serena │ ├── .gitignore │ ├── memories │ │ ├── pure_fsharp_output_conventions.md │ │ ├── active_pattern_design.md │ │ ├── quotation_generation_architecture.md │ │ ├── fsnative_integration_contract.md │ │ ├── native_binding_generation.md │ │ ├── xparsec_header_parsing.md │ │ └── farscape_barewire_integration.md │ └── project.yml ├── memories │ ├── pure_fsharp_output_conventions.md │ ├── active_pattern_design.md │ ├── quotation_generation_architecture.md │ ├── fsnative_integration_contract.md │ ├── native_binding_generation.md │ ├── xparsec_header_parsing.md │ └── farscape_barewire_integration.md └── project.yml ├── tests └── Farscape.Tests │ ├── Program.fs │ ├── Tests.fs │ └── Farscape.Tests.fsproj ├── img ├── Farscape_social.png └── Screenshot 2025-03-18 113946.png ├── global.json ├── src ├── Farscape.Core │ ├── Types.fs │ ├── ProjectOptions.fs │ ├── Farscape.Core.fsproj │ ├── BindingGenerator.fs │ ├── MemoryManager.fs │ ├── TypeMapper.fs │ ├── DelegatePointer.fs │ └── CodeGenerator.fs └── Farscape.Cli │ ├── Properties │ └── launchSettings.json │ ├── Farscape.Cli.fsproj │ └── Program.fs ├── PATENTS.md ├── Commercial.md ├── Farscape.sln ├── docs ├── README.md ├── 01_Architecture_Overview.md ├── 02_BAREWire_Integration.md └── 03_fsnative_Integration.md ├── .gitignore ├── LICENSE └── README.md /.serena/.gitignore: -------------------------------------------------------------------------------- 1 | /cache 2 | -------------------------------------------------------------------------------- /.serena/.serena/.gitignore: -------------------------------------------------------------------------------- 1 | /cache 2 | -------------------------------------------------------------------------------- /tests/Farscape.Tests/Program.fs: -------------------------------------------------------------------------------- 1 | module Program 2 | 3 | [] 4 | let main _ = 0 5 | -------------------------------------------------------------------------------- /img/Farscape_social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FidelityFramework/Farscape/HEAD/img/Farscape_social.png -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.103", 4 | "rollForward": "latestFeature" 5 | } 6 | } -------------------------------------------------------------------------------- /img/Screenshot 2025-03-18 113946.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FidelityFramework/Farscape/HEAD/img/Screenshot 2025-03-18 113946.png -------------------------------------------------------------------------------- /tests/Farscape.Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open System 4 | open Xunit 5 | 6 | [] 7 | let ``My test`` () = 8 | Assert.True(true) 9 | -------------------------------------------------------------------------------- /src/Farscape.Core/Types.fs: -------------------------------------------------------------------------------- 1 | namespace Farscape.Core 2 | 3 | module Types = 4 | type OperationStatus = 5 | | Success = 0 6 | | Error = 1 7 | | InvalidArgument = 2 8 | | NotImplemented = 3 9 | | NotSupported = 4 10 | | MemoryError = 5 11 | | TimeoutError = 6 -------------------------------------------------------------------------------- /src/Farscape.Core/ProjectOptions.fs: -------------------------------------------------------------------------------- 1 | namespace Farscape.Core 2 | 3 | module ProjectOptions = 4 | 5 | type ProjectOptions = { 6 | ProjectName: string 7 | Namespace: string 8 | OutputDirectory: string 9 | References: string list 10 | NuGetPackages: (string * string) list 11 | HeaderFile: string 12 | LibraryName: string 13 | IncludePaths: string list 14 | Verbose: bool 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/Farscape.Cli/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "WSL": { 4 | "commandName": "WSL2", 5 | "distributionName": "" 6 | }, 7 | "Farscape.Cli --help": { 8 | "commandName": "Project", 9 | "commandLineArgs": "generate --help" 10 | }, 11 | "Farscape.Cli test": { 12 | "commandName": "Project", 13 | "commandLineArgs": "generate --header \"c:/headerfile.h\" --library \"Lib\" --include-paths \"path1\" --include-paths \"path2\"" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Farscape.Core/Farscape.Core.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/Farscape.Tests/Farscape.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | false 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Farscape.Cli/Farscape.Cli.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | true 7 | farscape 8 | ./nupkg 9 | true 10 | 0.1.0 11 | You 12 | F# Native Library Binding Generator 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /PATENTS.md: -------------------------------------------------------------------------------- 1 | # Patent Notice for Farscape 2 | 3 | This file contains patent notices and license grants for the Farscape project. 4 | 5 | ## Fidelity Framework Patents 6 | 7 | Farscape is part of the Fidelity Framework for native F# compilation. The following patents are owned by SpeakEZ Technologies, Inc. and are applicable to the Fidelity Framework: 8 | 9 | 1. "System and Method for Zero-Copy Inter-Process Communication Using BARE Protocol" - U.S. Provisional Patent Application No. 63/786,247 10 | 11 | This patent covers the zero-copy IPC technology implemented in BAREWire. Farscape generates BAREWire peripheral descriptors which utilize this technology for memory-mapped hardware access. 12 | 13 | ## Patent Grant 14 | 15 | Subject to the terms and conditions of the applicable license agreement (either the Apache License 2.0 or a Commercial License), a patent license is granted by SpeakEZ Technologies, Inc. to the recipients of Farscape software. 16 | 17 | ### Apache License 2.0 Patent Grant 18 | 19 | If you are using Farscape under the Apache License 2.0, the patent license you receive is as defined in Section 3 of that license: 20 | 21 | > Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. 22 | > 23 | > If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 24 | 25 | ### Commercial License Patent Grant 26 | 27 | If you are using Farscape under a Commercial License, the patent license you receive is as defined in that Commercial License agreement. 28 | 29 | ## Additional Patent Information 30 | 31 | For further information regarding patent licensing, or to inquire about Commercial Licensing options, please contact: 32 | 33 | [SpeakEZ Technologies, Inc.](https://speakez.tech/contact) 34 | 35 | ## Defensive Termination 36 | 37 | Please note that the Apache License 2.0 contains a defensive termination clause that automatically terminates patent grants if you initiate litigation alleging that Farscape infringes a patent. 38 | 39 | --- 40 | 41 | This PATENTS.md file is separate from and in addition to the LICENSE file that accompanies the Farscape software. It does not modify or replace the terms of that file. 42 | 43 | For information about obtaining a Commercial License, please refer to the 44 | Commercial.md file included with this software or contact: 45 | 46 | [SpeakEZ Technologies, Inc.](https://speakez.tech/contact) 47 | -------------------------------------------------------------------------------- /.serena/memories/pure_fsharp_output_conventions.md: -------------------------------------------------------------------------------- 1 | # Farscape Pure F# Output Conventions 2 | 3 | ## Core Principle: Generate Pure F# 4 | 5 | All Farscape output must use pure F# idioms: 6 | - Records, not interfaces 7 | - Discriminated unions, not enums with attributes 8 | - Module functions, not static methods 9 | - Active patterns, not visitor patterns 10 | 11 | ## What Farscape Generates 12 | 13 | ### ✅ Record Types 14 | ```fsharp 15 | // Generated MemoryModel 16 | type MemoryModel = { 17 | TargetFamily: string 18 | PeripheralDescriptors: Expr list 19 | RegisterConstraints: Expr list 20 | Regions: Expr 21 | Recognize: PSGNode -> MemoryOperation option 22 | CacheTopology: Expr option 23 | CoherencyModel: Expr option 24 | } 25 | ``` 26 | 27 | ### ✅ Discriminated Unions 28 | ```fsharp 29 | // Generated from C enum 30 | type GPIO_PinState = 31 | | Reset 32 | | Set 33 | 34 | // Generated operation type 35 | type GpioOperation = 36 | | Init of GpioInitInfo 37 | | WritePin of GpioWritePinInfo 38 | | ReadPin of GpioReadPinInfo 39 | | TogglePin of GpioTogglePinInfo 40 | ``` 41 | 42 | ### ✅ Active Patterns 43 | ```fsharp 44 | // Generated recognition patterns 45 | let (|HalGpioWritePin|_|) (node: PSGNode) : GpioWritePinInfo option = ... 46 | let (|PeripheralAccess|_|) (node: PSGNode) : PeripheralAccessInfo option = ... 47 | ``` 48 | 49 | ### ✅ Module Functions 50 | ```fsharp 51 | // Generated API module 52 | module GPIO = 53 | let writePin port pin state = ... 54 | let readPin port pin = ... 55 | let togglePin port pin = ... 56 | ``` 57 | 58 | ### ✅ Quotations 59 | ```fsharp 60 | // Generated memory descriptors 61 | let gpioQuotation: Expr = <@ ... @> 62 | ``` 63 | 64 | ## What Farscape Does NOT Generate 65 | 66 | ### ❌ Interfaces 67 | ```fsharp 68 | // WRONG - don't generate this 69 | type IPeripheralProvider = 70 | abstract GetDescriptor: unit -> PeripheralDescriptor 71 | ``` 72 | 73 | ### ❌ Abstract Classes 74 | ```fsharp 75 | // WRONG - don't generate this 76 | [] 77 | type PeripheralBase() = 78 | abstract GetRegisters: unit -> Register list 79 | ``` 80 | 81 | ### ❌ I-Prefix Names 82 | ```fsharp 83 | // WRONG 84 | type IMemoryModel = ... 85 | 86 | // RIGHT 87 | type MemoryModel = ... 88 | ``` 89 | 90 | ### ❌ BCL Attributes (unnecessary ones) 91 | ```fsharp 92 | // WRONG 93 | [] 94 | type GpioConfig = ... 95 | 96 | // RIGHT - just the record 97 | type GpioConfig = { ... } 98 | ``` 99 | 100 | ## Acceptable Attributes 101 | 102 | ```fsharp 103 | [] // Value type optimization - OK 104 | [] // Module access control - OK 105 | [] // Convenience - OK 106 | [] // Units of measure - OK 107 | ``` 108 | 109 | ## Platform Bindings Convention 110 | 111 | For extern declarations, use the module convention (not DllImport): 112 | 113 | ```fsharp 114 | // Generated Platform.Bindings module 115 | module Platform.Bindings = 116 | let halGpioInit gpio init : unit = Unchecked.defaultof 117 | let halGpioWritePin gpio pin state : unit = Unchecked.defaultof 118 | let halGpioReadPin gpio pin : GPIO_PinState = Unchecked.defaultof 119 | ``` 120 | 121 | Alex recognizes this pattern and provides platform-specific implementations. 122 | 123 | ## Why Pure F#? 124 | 125 | 1. **Native Compilation**: BCL patterns require runtime that doesn't exist in Fidelity 126 | 2. **F* Compatibility**: Future proof annotations will be attributes - minimize attribute clutter 127 | 3. **Quotation Friendly**: Records and DUs quote cleanly; interfaces don't 128 | 4. **Composition**: Pure F# composes better than OO hierarchies 129 | 130 | ## Canonical Reference 131 | 132 | See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md`, "Design Principle 4: Pure F# Idioms" 133 | -------------------------------------------------------------------------------- /.serena/.serena/memories/pure_fsharp_output_conventions.md: -------------------------------------------------------------------------------- 1 | # Farscape Pure F# Output Conventions 2 | 3 | ## Core Principle: Generate Pure F# 4 | 5 | All Farscape output must use pure F# idioms: 6 | - Records, not interfaces 7 | - Discriminated unions, not enums with attributes 8 | - Module functions, not static methods 9 | - Active patterns, not visitor patterns 10 | 11 | ## What Farscape Generates 12 | 13 | ### ✅ Record Types 14 | ```fsharp 15 | // Generated MemoryModel 16 | type MemoryModel = { 17 | TargetFamily: string 18 | PeripheralDescriptors: Expr list 19 | RegisterConstraints: Expr list 20 | Regions: Expr 21 | Recognize: PSGNode -> MemoryOperation option 22 | CacheTopology: Expr option 23 | CoherencyModel: Expr option 24 | } 25 | ``` 26 | 27 | ### ✅ Discriminated Unions 28 | ```fsharp 29 | // Generated from C enum 30 | type GPIO_PinState = 31 | | Reset 32 | | Set 33 | 34 | // Generated operation type 35 | type GpioOperation = 36 | | Init of GpioInitInfo 37 | | WritePin of GpioWritePinInfo 38 | | ReadPin of GpioReadPinInfo 39 | | TogglePin of GpioTogglePinInfo 40 | ``` 41 | 42 | ### ✅ Active Patterns 43 | ```fsharp 44 | // Generated recognition patterns 45 | let (|HalGpioWritePin|_|) (node: PSGNode) : GpioWritePinInfo option = ... 46 | let (|PeripheralAccess|_|) (node: PSGNode) : PeripheralAccessInfo option = ... 47 | ``` 48 | 49 | ### ✅ Module Functions 50 | ```fsharp 51 | // Generated API module 52 | module GPIO = 53 | let writePin port pin state = ... 54 | let readPin port pin = ... 55 | let togglePin port pin = ... 56 | ``` 57 | 58 | ### ✅ Quotations 59 | ```fsharp 60 | // Generated memory descriptors 61 | let gpioQuotation: Expr = <@ ... @> 62 | ``` 63 | 64 | ## What Farscape Does NOT Generate 65 | 66 | ### ❌ Interfaces 67 | ```fsharp 68 | // WRONG - don't generate this 69 | type IPeripheralProvider = 70 | abstract GetDescriptor: unit -> PeripheralDescriptor 71 | ``` 72 | 73 | ### ❌ Abstract Classes 74 | ```fsharp 75 | // WRONG - don't generate this 76 | [] 77 | type PeripheralBase() = 78 | abstract GetRegisters: unit -> Register list 79 | ``` 80 | 81 | ### ❌ I-Prefix Names 82 | ```fsharp 83 | // WRONG 84 | type IMemoryModel = ... 85 | 86 | // RIGHT 87 | type MemoryModel = ... 88 | ``` 89 | 90 | ### ❌ BCL Attributes (unnecessary ones) 91 | ```fsharp 92 | // WRONG 93 | [] 94 | type GpioConfig = ... 95 | 96 | // RIGHT - just the record 97 | type GpioConfig = { ... } 98 | ``` 99 | 100 | ## Acceptable Attributes 101 | 102 | ```fsharp 103 | [] // Value type optimization - OK 104 | [] // Module access control - OK 105 | [] // Convenience - OK 106 | [] // Units of measure - OK 107 | ``` 108 | 109 | ## Platform Bindings Convention 110 | 111 | For extern declarations, use the module convention (not DllImport): 112 | 113 | ```fsharp 114 | // Generated Platform.Bindings module 115 | module Platform.Bindings = 116 | let halGpioInit gpio init : unit = Unchecked.defaultof 117 | let halGpioWritePin gpio pin state : unit = Unchecked.defaultof 118 | let halGpioReadPin gpio pin : GPIO_PinState = Unchecked.defaultof 119 | ``` 120 | 121 | Alex recognizes this pattern and provides platform-specific implementations. 122 | 123 | ## Why Pure F#? 124 | 125 | 1. **Native Compilation**: BCL patterns require runtime that doesn't exist in Fidelity 126 | 2. **F* Compatibility**: Future proof annotations will be attributes - minimize attribute clutter 127 | 3. **Quotation Friendly**: Records and DUs quote cleanly; interfaces don't 128 | 4. **Composition**: Pure F# composes better than OO hierarchies 129 | 130 | ## Canonical Reference 131 | 132 | See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md`, "Design Principle 4: Pure F# Idioms" 133 | -------------------------------------------------------------------------------- /src/Farscape.Core/BindingGenerator.fs: -------------------------------------------------------------------------------- 1 | namespace Farscape.Core 2 | 3 | open System.IO 4 | open ProjectOptions 5 | open CodeGenerator 6 | 7 | 8 | module BindingGenerator = 9 | 10 | type GenerationOptions = { 11 | HeaderFile: FileInfo 12 | LibraryName: string 13 | OutputDirectory: string 14 | Namespace: string 15 | IncludePaths: string list 16 | Defines: string list 17 | Verbose: bool 18 | } 19 | 20 | let extractStructTypes (declarations: CppParser.Declaration list) : string list = 21 | let rec collect (decls: CppParser.Declaration list) = 22 | [ 23 | for decl in decls do 24 | match decl with 25 | | CppParser.Declaration.Struct s -> yield s.Name 26 | | CppParser.Declaration.Namespace ns -> yield! collect ns.Declarations 27 | | CppParser.Declaration.Class c when c.Methods.IsEmpty -> yield c.Name 28 | | _ -> () 29 | ] 30 | collect declarations 31 | |> List.distinct 32 | 33 | let logVerbose (message: string) (verbose: bool) = 34 | if verbose then 35 | printfn "%s" message 36 | 37 | /// Result type for binding generation 38 | type GenerationResult = { 39 | SolutionPath: string 40 | LibraryPath: string 41 | TestPath: string 42 | DeclarationCount: int 43 | } 44 | 45 | /// Generate F# bindings from a C/C++ header file 46 | /// Returns Result to enforce proper error handling - fails fast on parse errors 47 | let generateBindings (options: GenerationOptions) : Result = 48 | logVerbose $"Starting binding generation for {options.HeaderFile}" options.Verbose 49 | logVerbose $"Target library: {options.LibraryName}" options.Verbose 50 | logVerbose $"Output directory: {options.OutputDirectory}" options.Verbose 51 | logVerbose $"Namespace: {options.Namespace}" options.Verbose 52 | 53 | logVerbose "Parsing header file..." options.Verbose 54 | 55 | match CppParser.parseWithDefines options.HeaderFile.FullName options.IncludePaths options.Defines options.Verbose with 56 | | Error parseError -> 57 | Error $"Failed to parse header: {parseError}" 58 | | Ok declarations -> 59 | logVerbose $"Successfully parsed {declarations.Length} declarations" options.Verbose 60 | 61 | Directory.CreateDirectory(options.OutputDirectory) |> ignore 62 | 63 | logVerbose "Generating F# code..." options.Verbose 64 | let generatedCode = generateCode declarations options.Namespace options.LibraryName 65 | 66 | logVerbose "Creating project files..." options.Verbose 67 | let projectOptions : ProjectOptions = { 68 | ProjectName = options.LibraryName 69 | Namespace = options.Namespace 70 | OutputDirectory = options.OutputDirectory 71 | References = [] 72 | NuGetPackages = [ 73 | ("System.Memory", "4.5.5") 74 | ("System.Runtime.CompilerServices.Unsafe", "6.0.0") 75 | ] 76 | HeaderFile = options.HeaderFile.FullName 77 | LibraryName = options.LibraryName 78 | IncludePaths = options.IncludePaths 79 | Verbose = options.Verbose 80 | } 81 | 82 | let (solutionPath, libraryPath, testPath) = Project.generateProject projectOptions generatedCode 83 | 84 | logVerbose "Binding generation completed successfully." options.Verbose 85 | logVerbose $"Solution generated at: {solutionPath}" options.Verbose 86 | logVerbose $"Library project generated at: {libraryPath}" options.Verbose 87 | logVerbose $"Test project generated at: {testPath}" options.Verbose 88 | 89 | Ok { 90 | SolutionPath = solutionPath 91 | LibraryPath = libraryPath 92 | TestPath = testPath 93 | DeclarationCount = declarations.Length 94 | } -------------------------------------------------------------------------------- /Commercial.md: -------------------------------------------------------------------------------- 1 | # Farscape Commercial License 2 | 3 | ## Overview 4 | 5 | Farscape is available under a Commercial License for use in commercial products, services, and enterprise environments. This Commercial License provides rights and protections beyond those offered by the Apache License 2.0. 6 | 7 | ## Commercial License Benefits 8 | 9 | The Commercial License for Farscape provides: 10 | 11 | - **Expanded Usage Rights**: Full rights to use, modify, and distribute Farscape in commercial products and services without the restrictions of the Apache License 2.0 12 | 13 | - **Patent Rights**: Explicit patent license covering the Fidelity Framework technology, including the "System and Method for Zero-Copy Inter-Process Communication Using BARE Protocol" (US Patent Application No. 63/786,247) 14 | 15 | - **No Open Source Requirements**: Freedom from the obligation to open source your own code that integrates with Farscape 16 | 17 | - **Legal Indemnification**: Protection against intellectual property claims related to your use of Farscape (available with Enterprise support tiers) 18 | 19 | - **Priority Support**: Access to technical support, bug fixes, and security updates 20 | 21 | - **Customization Options**: Ability to request custom features and modifications 22 | 23 | - **Access to Enterprise Features**: Additional functionality not available in the open source version 24 | 25 | ## Licensing Models 26 | 27 | Farscape Commercial Licenses are available in several tiers: 28 | 29 | ### Standard Commercial License 30 | - Suitable for commercial products and services 31 | - Includes patent license and basic support 32 | - Annual subscription based on deployment size 33 | 34 | ### Enterprise License 35 | - Designed for large-scale enterprise deployments 36 | - Includes indemnification, priority support, and all Enterprise features 37 | - Custom pricing based on usage metrics 38 | 39 | ### OEM/Embedded License 40 | - For embedding Farscape within products distributed to customers 41 | - Special terms for high-volume deployments 42 | - Custom pricing based on distribution model 43 | 44 | ## How to Obtain a Commercial License 45 | 46 | To discuss your specific needs and obtain pricing information for a Commercial License, please contact: 47 | 48 | [SpeakEZ Technologies, Inc.](https://speakez.tech/contact) 49 | 50 | When contacting us, please provide: 51 | 1. Your organization name 52 | 2. Intended use case for Farscape 53 | 3. Estimated deployment size/scope 54 | 4. Any specific requirements or questions 55 | 56 | ## Commercial Terms and Conditions 57 | 58 | The Commercial License is subject to standard terms and conditions, including: 59 | 60 | - License fees are typically structured as annual subscriptions 61 | - Licenses are per legal entity unless otherwise specified 62 | - Technical support is provided according to the terms of your specific license tier 63 | - Custom terms are available for special use cases 64 | 65 | ## Frequently Asked Questions 66 | 67 | **Q: Do I need a Commercial License?** 68 | 69 | A: You need a Commercial License if you are using Farscape in a commercial product or service, if you want commercial support, or if you need to distribute Farscape as part of a proprietary solution that you do not want to open source. 70 | 71 | **Q: Can I try Farscape before purchasing a Commercial License?** 72 | 73 | A: Yes, you can evaluate Farscape under the Apache License 2.0 for a limited period before deciding to purchase a Commercial License. 74 | 75 | **Q: What happens when my Commercial License expires?** 76 | 77 | A: You will need to renew your license to continue using Farscape commercially. Without renewal, you would need to transition to using Farscape under the Apache License 2.0 and comply with all its terms. 78 | 79 | **Q: Can I upgrade from one license tier to another?** 80 | 81 | A: Yes, you can upgrade your license tier at any time. The cost difference will be prorated based on the remaining term of your current license. 82 | 83 | **Q: Does the Farscape Commercial License cover the entire Fidelity Framework?** 84 | 85 | A: The Farscape Commercial License covers Farscape itself. The Fidelity Framework includes other components (Firefly, Alloy, BAREWire) which are separately licensed but available under similar terms. Contact us for bundled licensing options. 86 | -------------------------------------------------------------------------------- /Farscape.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Farscape.Core", "src\Farscape.Core\Farscape.Core.fsproj", "{33618CF1-2126-83D3-D824-A9795080765A}" 9 | EndProject 10 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Farscape.Cli", "src\Farscape.Cli\Farscape.Cli.fsproj", "{96939BC0-D8BB-C52F-45F2-4181238F149A}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" 13 | EndProject 14 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Farscape.Tests", "tests\Farscape.Tests\Farscape.Tests.fsproj", "{E43012DF-C51B-3F27-E29B-539F9F669110}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|x64 = Debug|x64 20 | Debug|x86 = Debug|x86 21 | Release|Any CPU = Release|Any CPU 22 | Release|x64 = Release|x64 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {33618CF1-2126-83D3-D824-A9795080765A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {33618CF1-2126-83D3-D824-A9795080765A}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {33618CF1-2126-83D3-D824-A9795080765A}.Debug|x64.ActiveCfg = Debug|Any CPU 29 | {33618CF1-2126-83D3-D824-A9795080765A}.Debug|x64.Build.0 = Debug|Any CPU 30 | {33618CF1-2126-83D3-D824-A9795080765A}.Debug|x86.ActiveCfg = Debug|Any CPU 31 | {33618CF1-2126-83D3-D824-A9795080765A}.Debug|x86.Build.0 = Debug|Any CPU 32 | {33618CF1-2126-83D3-D824-A9795080765A}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {33618CF1-2126-83D3-D824-A9795080765A}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {33618CF1-2126-83D3-D824-A9795080765A}.Release|x64.ActiveCfg = Release|Any CPU 35 | {33618CF1-2126-83D3-D824-A9795080765A}.Release|x64.Build.0 = Release|Any CPU 36 | {33618CF1-2126-83D3-D824-A9795080765A}.Release|x86.ActiveCfg = Release|Any CPU 37 | {33618CF1-2126-83D3-D824-A9795080765A}.Release|x86.Build.0 = Release|Any CPU 38 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Debug|x64.ActiveCfg = Debug|Any CPU 41 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Debug|x64.Build.0 = Debug|Any CPU 42 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Debug|x86.ActiveCfg = Debug|Any CPU 43 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Debug|x86.Build.0 = Debug|Any CPU 44 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Release|x64.ActiveCfg = Release|Any CPU 47 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Release|x64.Build.0 = Release|Any CPU 48 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Release|x86.ActiveCfg = Release|Any CPU 49 | {96939BC0-D8BB-C52F-45F2-4181238F149A}.Release|x86.Build.0 = Release|Any CPU 50 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Debug|x64.ActiveCfg = Debug|Any CPU 53 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Debug|x64.Build.0 = Debug|Any CPU 54 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Debug|x86.ActiveCfg = Debug|Any CPU 55 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Debug|x86.Build.0 = Debug|Any CPU 56 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Release|x64.ActiveCfg = Release|Any CPU 59 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Release|x64.Build.0 = Release|Any CPU 60 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Release|x86.ActiveCfg = Release|Any CPU 61 | {E43012DF-C51B-3F27-E29B-539F9F669110}.Release|x86.Build.0 = Release|Any CPU 62 | EndGlobalSection 63 | GlobalSection(SolutionProperties) = preSolution 64 | HideSolutionNode = FALSE 65 | EndGlobalSection 66 | GlobalSection(NestedProjects) = preSolution 67 | {33618CF1-2126-83D3-D824-A9795080765A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} 68 | {96939BC0-D8BB-C52F-45F2-4181238F149A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} 69 | {E43012DF-C51B-3F27-E29B-539F9F669110} = {0AB3BF05-4346-4AA6-1389-037BE0695223} 70 | EndGlobalSection 71 | EndGlobal 72 | -------------------------------------------------------------------------------- /src/Farscape.Core/MemoryManager.fs: -------------------------------------------------------------------------------- 1 | namespace Farscape.Core 2 | 3 | open System 4 | open System.Runtime.InteropServices 5 | open System.Runtime.CompilerServices 6 | 7 | module MemoryManager = 8 | 9 | let generatePinnedStructArray (structName: string) (elementSize: int) = 10 | $""" 11 | /// Allocates a pinned array of {structName} on the pinned object heap 12 | let allocatePinned{structName}Array (count: int) : {structName}[] = 13 | let array = GC.AllocateArray<{structName}>(count, true) 14 | array 15 | """ 16 | 17 | let generateGetPinnedArrayPointer (structName: string) = 18 | $""" 19 | /// Gets a pointer to the first element of a pinned {structName} array 20 | let get{structName}ArrayPointer (array: {structName}[]) : nativeint = 21 | GCHandle.Alloc(array, GCHandleType.Pinned).AddrOfPinnedObject() 22 | """ 23 | 24 | let generateMemoryCast (sourceType: string) (targetType: string) = 25 | $""" 26 | /// Cast a Span<{sourceType}> to a Span<{targetType}> 27 | let cast{sourceType}To{targetType} (source: Span<{sourceType}>) : Span<{targetType}> = 28 | MemoryMarshal.Cast<{sourceType}, {targetType}>(source) 29 | """ 30 | 31 | let generateFixedStatement (structName: string) = 32 | $""" 33 | /// Performs an operation with a fixed pointer to a {structName} 34 | let fixed{structName} (value: {structName}) (action: nativeint -> 'T) : 'T = 35 | let valueArr = [|value|] 36 | let handle = GCHandle.Alloc(valueArr, GCHandleType.Pinned) 37 | try 38 | let ptr = handle.AddrOfPinnedObject() 39 | action ptr 40 | finally 41 | handle.Free() 42 | """ 43 | 44 | let generateSpanFromPointer (structName: string) = 45 | $""" 46 | /// Creates a Span<{structName}> from a pointer and length 47 | let spanFrom{structName}Pointer (ptr: nativeint) (length: int) : Span<{structName}> = 48 | MemoryMarshal.CreateSpan(ref Unsafe.AsRef<{structName}>(ptr.ToPointer()), length) 49 | """ 50 | 51 | let generateFreeNativeMemory = 52 | """ 53 | /// Frees memory allocated with Marshal.AllocHGlobal 54 | let freeNativeMemory (ptr: nativeint) : unit = 55 | if ptr <> IntPtr.Zero then 56 | Marshal.FreeHGlobal(ptr) 57 | """ 58 | 59 | let generateAllocateNativeMemory = 60 | """ 61 | /// Allocates memory using Marshal.AllocHGlobal 62 | let allocateNativeMemory (size: int) : nativeint = 63 | Marshal.AllocHGlobal(size) 64 | """ 65 | 66 | let generateCopyToNativeMemory (structName: string) = 67 | $""" 68 | /// Copies a {structName} to native memory 69 | let copyToNativeMemory (value: {structName}) : nativeint = 70 | let size = Marshal.SizeOf<{structName}>() 71 | let ptr = Marshal.AllocHGlobal(size) 72 | Marshal.StructureToPtr(value, ptr, false) 73 | ptr 74 | """ 75 | 76 | let generateCopyFromNativeMemory (structName: string) = 77 | $""" 78 | /// Copies a {structName} from native memory 79 | let copyFromNativeMemory (ptr: nativeint) : {structName} = 80 | Marshal.PtrToStructure<{structName}>(ptr) 81 | """ 82 | 83 | let generateMarshalStructArray (structName: string) = 84 | $""" 85 | /// Copies an array of {structName} to native memory 86 | let marshalStructArray (array: {structName}[]) : nativeint = 87 | let elementSize = Marshal.SizeOf<{structName}>() 88 | let totalSize = elementSize * array.Length 89 | let ptr = Marshal.AllocHGlobal(totalSize) 90 | 91 | for i = 0 to array.Length - 1 do 92 | let elementPtr = IntPtr.op_Addition(ptr, i * elementSize) 93 | Marshal.StructureToPtr(array.[i], elementPtr, false) 94 | 95 | ptr 96 | """ 97 | 98 | let generateMemoryManagement (structTypes: string list) = 99 | let sb = System.Text.StringBuilder() 100 | 101 | // Add standard memory management functions 102 | sb.AppendLine(generateAllocateNativeMemory) |> ignore 103 | sb.AppendLine() |> ignore 104 | sb.AppendLine(generateFreeNativeMemory) |> ignore 105 | sb.AppendLine() |> ignore 106 | 107 | // Add type-specific memory management functions 108 | for structType in structTypes do 109 | sb.AppendLine(generatePinnedStructArray structType 0) |> ignore 110 | sb.AppendLine() |> ignore 111 | sb.AppendLine(generateGetPinnedArrayPointer structType) |> ignore 112 | sb.AppendLine() |> ignore 113 | sb.AppendLine(generateFixedStatement structType) |> ignore 114 | sb.AppendLine() |> ignore 115 | sb.AppendLine(generateCopyToNativeMemory structType) |> ignore 116 | sb.AppendLine() |> ignore 117 | sb.AppendLine(generateCopyFromNativeMemory structType) |> ignore 118 | sb.AppendLine() |> ignore 119 | sb.AppendLine(generateMarshalStructArray structType) |> ignore 120 | sb.AppendLine() |> ignore 121 | 122 | sb.ToString() -------------------------------------------------------------------------------- /.serena/memories/active_pattern_design.md: -------------------------------------------------------------------------------- 1 | # Active Pattern Design for PSG Recognition 2 | 3 | ## Why Active Patterns? 4 | 5 | Active patterns are F#'s "hidden jewel" for compositional pattern matching. For Farscape, they provide: 6 | 7 | 1. **Compositional Recognition**: Patterns compose naturally 8 | 2. **Type-Safe Extraction**: Compiler verifies pattern matching 9 | 3. **Extensibility**: New patterns add without modifying existing code 10 | 4. **PSG Integration**: Natural fit for Firefly's semantic graph traversal 11 | 12 | ## Pattern Categories 13 | 14 | ### 1. Function Call Patterns 15 | 16 | Match specific extern function calls: 17 | 18 | ```fsharp 19 | let (|HalGpioInit|_|) (node: PSGNode) = 20 | match node with 21 | | CallToExtern "HAL_GPIO_Init" [gpio; init] -> 22 | Some { Port = extractPort gpio; Config = extractInitConfig init } 23 | | _ -> None 24 | 25 | let (|HalGpioWritePin|_|) (node: PSGNode) = 26 | match node with 27 | | CallToExtern "HAL_GPIO_WritePin" [gpio; pin; state] -> 28 | Some { Port = extractPort gpio; Pin = extractPin pin; State = extractState state } 29 | | _ -> None 30 | ``` 31 | 32 | ### 2. Memory Access Patterns 33 | 34 | Match register read/write operations: 35 | 36 | ```fsharp 37 | let (|RegisterRead|_|) (node: PSGNode) = 38 | match node with 39 | | PointerDeref { Base = PeripheralBase base; Offset = offset } -> 40 | Some { Address = base + offset; Width = inferWidth node } 41 | | _ -> None 42 | 43 | let (|RegisterWrite|_|) (node: PSGNode) = 44 | match node with 45 | | Assignment { Target = PointerDeref { Base = PeripheralBase base; Offset = offset }; Value = value } -> 46 | Some { Address = base + offset; Value = value; Width = inferWidth node } 47 | | _ -> None 48 | ``` 49 | 50 | ### 3. Constraint Validation Patterns 51 | 52 | Validate access constraints at compile time: 53 | 54 | ```fsharp 55 | let (|ReadOnlyViolation|_|) (node: PSGNode) = 56 | match node with 57 | | RegisterWrite { Address = addr } when isReadOnlyRegister addr -> 58 | Some { Register = lookupRegister addr; Operation = "write" } 59 | | _ -> None 60 | 61 | let (|WriteOnlyViolation|_|) (node: PSGNode) = 62 | match node with 63 | | RegisterRead { Address = addr } when isWriteOnlyRegister addr -> 64 | Some { Register = lookupRegister addr; Operation = "read" } 65 | | _ -> None 66 | ``` 67 | 68 | ### 4. Composite Patterns 69 | 70 | Combine simpler patterns: 71 | 72 | ```fsharp 73 | let (|GpioOperation|_|) node = 74 | match node with 75 | | HalGpioInit info -> Some (GpioInit info) 76 | | HalGpioWritePin info -> Some (GpioWrite info) 77 | | HalGpioReadPin info -> Some (GpioRead info) 78 | | HalGpioTogglePin info -> Some (GpioToggle info) 79 | | _ -> None 80 | 81 | let (|PeripheralOperation|_|) node = 82 | match node with 83 | | GpioOperation op -> Some (GPIO op) 84 | | UsartOperation op -> Some (USART op) 85 | | SpiOperation op -> Some (SPI op) 86 | | I2cOperation op -> Some (I2C op) 87 | | _ -> None 88 | ``` 89 | 90 | ## Integration with MemoryModel 91 | 92 | The `Recognize` function in `MemoryModel` uses active patterns: 93 | 94 | ```fsharp 95 | let recognizeSTM32L5MemoryOperation (node: PSGNode) : MemoryOperation option = 96 | match node with 97 | | PeripheralOperation op -> Some (PeripheralOp op) 98 | | DmaOperation op -> Some (DmaOp op) 99 | | SystemControlOperation op -> Some (SystemOp op) 100 | | _ -> None 101 | 102 | let stm32l5MemoryModel: MemoryModel = { 103 | // ... 104 | Recognize = recognizeSTM32L5MemoryOperation 105 | // ... 106 | } 107 | ``` 108 | 109 | ## Generation from C Headers 110 | 111 | Farscape generates patterns from parsed function signatures: 112 | 113 | ```c 114 | // C header 115 | void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState); 116 | ``` 117 | 118 | Generates: 119 | 120 | ```fsharp 121 | // Pattern definition 122 | let (|HalGpioWritePin|_|) (node: PSGNode) : GpioWritePinInfo option = 123 | match node with 124 | | CallToExtern "HAL_GPIO_WritePin" [gpio; pin; state] -> 125 | Some { 126 | Port = extractGpioPort gpio 127 | Pin = extractUInt16 pin 128 | State = extractPinState state 129 | } 130 | | _ -> None 131 | 132 | // Info type 133 | type GpioWritePinInfo = { 134 | Port: string 135 | Pin: uint16 136 | State: PinState 137 | } 138 | ``` 139 | 140 | ## Pattern Naming Convention 141 | 142 | | C Function | Active Pattern | Info Type | 143 | |------------|----------------|-----------| 144 | | `HAL_GPIO_Init` | `(|HalGpioInit|_|)` | `GpioInitInfo` | 145 | | `HAL_GPIO_WritePin` | `(|HalGpioWritePin|_|)` | `GpioWritePinInfo` | 146 | | `HAL_UART_Transmit` | `(|HalUartTransmit|_|)` | `UartTransmitInfo` | 147 | 148 | ## Canonical Reference 149 | 150 | See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md`, section "Active Patterns: The Recognition Substrate" 151 | -------------------------------------------------------------------------------- /.serena/.serena/memories/active_pattern_design.md: -------------------------------------------------------------------------------- 1 | # Active Pattern Design for PSG Recognition 2 | 3 | ## Why Active Patterns? 4 | 5 | Active patterns are F#'s "hidden jewel" for compositional pattern matching. For Farscape, they provide: 6 | 7 | 1. **Compositional Recognition**: Patterns compose naturally 8 | 2. **Type-Safe Extraction**: Compiler verifies pattern matching 9 | 3. **Extensibility**: New patterns add without modifying existing code 10 | 4. **PSG Integration**: Natural fit for Firefly's semantic graph traversal 11 | 12 | ## Pattern Categories 13 | 14 | ### 1. Function Call Patterns 15 | 16 | Match specific extern function calls: 17 | 18 | ```fsharp 19 | let (|HalGpioInit|_|) (node: PSGNode) = 20 | match node with 21 | | CallToExtern "HAL_GPIO_Init" [gpio; init] -> 22 | Some { Port = extractPort gpio; Config = extractInitConfig init } 23 | | _ -> None 24 | 25 | let (|HalGpioWritePin|_|) (node: PSGNode) = 26 | match node with 27 | | CallToExtern "HAL_GPIO_WritePin" [gpio; pin; state] -> 28 | Some { Port = extractPort gpio; Pin = extractPin pin; State = extractState state } 29 | | _ -> None 30 | ``` 31 | 32 | ### 2. Memory Access Patterns 33 | 34 | Match register read/write operations: 35 | 36 | ```fsharp 37 | let (|RegisterRead|_|) (node: PSGNode) = 38 | match node with 39 | | PointerDeref { Base = PeripheralBase base; Offset = offset } -> 40 | Some { Address = base + offset; Width = inferWidth node } 41 | | _ -> None 42 | 43 | let (|RegisterWrite|_|) (node: PSGNode) = 44 | match node with 45 | | Assignment { Target = PointerDeref { Base = PeripheralBase base; Offset = offset }; Value = value } -> 46 | Some { Address = base + offset; Value = value; Width = inferWidth node } 47 | | _ -> None 48 | ``` 49 | 50 | ### 3. Constraint Validation Patterns 51 | 52 | Validate access constraints at compile time: 53 | 54 | ```fsharp 55 | let (|ReadOnlyViolation|_|) (node: PSGNode) = 56 | match node with 57 | | RegisterWrite { Address = addr } when isReadOnlyRegister addr -> 58 | Some { Register = lookupRegister addr; Operation = "write" } 59 | | _ -> None 60 | 61 | let (|WriteOnlyViolation|_|) (node: PSGNode) = 62 | match node with 63 | | RegisterRead { Address = addr } when isWriteOnlyRegister addr -> 64 | Some { Register = lookupRegister addr; Operation = "read" } 65 | | _ -> None 66 | ``` 67 | 68 | ### 4. Composite Patterns 69 | 70 | Combine simpler patterns: 71 | 72 | ```fsharp 73 | let (|GpioOperation|_|) node = 74 | match node with 75 | | HalGpioInit info -> Some (GpioInit info) 76 | | HalGpioWritePin info -> Some (GpioWrite info) 77 | | HalGpioReadPin info -> Some (GpioRead info) 78 | | HalGpioTogglePin info -> Some (GpioToggle info) 79 | | _ -> None 80 | 81 | let (|PeripheralOperation|_|) node = 82 | match node with 83 | | GpioOperation op -> Some (GPIO op) 84 | | UsartOperation op -> Some (USART op) 85 | | SpiOperation op -> Some (SPI op) 86 | | I2cOperation op -> Some (I2C op) 87 | | _ -> None 88 | ``` 89 | 90 | ## Integration with MemoryModel 91 | 92 | The `Recognize` function in `MemoryModel` uses active patterns: 93 | 94 | ```fsharp 95 | let recognizeSTM32L5MemoryOperation (node: PSGNode) : MemoryOperation option = 96 | match node with 97 | | PeripheralOperation op -> Some (PeripheralOp op) 98 | | DmaOperation op -> Some (DmaOp op) 99 | | SystemControlOperation op -> Some (SystemOp op) 100 | | _ -> None 101 | 102 | let stm32l5MemoryModel: MemoryModel = { 103 | // ... 104 | Recognize = recognizeSTM32L5MemoryOperation 105 | // ... 106 | } 107 | ``` 108 | 109 | ## Generation from C Headers 110 | 111 | Farscape generates patterns from parsed function signatures: 112 | 113 | ```c 114 | // C header 115 | void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState); 116 | ``` 117 | 118 | Generates: 119 | 120 | ```fsharp 121 | // Pattern definition 122 | let (|HalGpioWritePin|_|) (node: PSGNode) : GpioWritePinInfo option = 123 | match node with 124 | | CallToExtern "HAL_GPIO_WritePin" [gpio; pin; state] -> 125 | Some { 126 | Port = extractGpioPort gpio 127 | Pin = extractUInt16 pin 128 | State = extractPinState state 129 | } 130 | | _ -> None 131 | 132 | // Info type 133 | type GpioWritePinInfo = { 134 | Port: string 135 | Pin: uint16 136 | State: PinState 137 | } 138 | ``` 139 | 140 | ## Pattern Naming Convention 141 | 142 | | C Function | Active Pattern | Info Type | 143 | |------------|----------------|-----------| 144 | | `HAL_GPIO_Init` | `(|HalGpioInit|_|)` | `GpioInitInfo` | 145 | | `HAL_GPIO_WritePin` | `(|HalGpioWritePin|_|)` | `GpioWritePinInfo` | 146 | | `HAL_UART_Transmit` | `(|HalUartTransmit|_|)` | `UartTransmitInfo` | 147 | 148 | ## Canonical Reference 149 | 150 | See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md`, section "Active Patterns: The Recognition Substrate" 151 | -------------------------------------------------------------------------------- /.serena/memories/quotation_generation_architecture.md: -------------------------------------------------------------------------------- 1 | # Farscape Quotation Generation Architecture 2 | 3 | ## Primary Mission 4 | 5 | Farscape transforms C/C++ headers into **F# quotations and active patterns** for the Fidelity nanopass pipeline. 6 | 7 | This is NOT traditional FFI generation. Farscape produces: 8 | 1. `Expr` quotations encoding memory layout 9 | 2. Active patterns for PSG node recognition 10 | 3. `MemoryModel` records for fsnative integration 11 | 12 | ## The Four Outputs 13 | 14 | ### 1. Quotations (Expr) 15 | 16 | Quotations carry memory constraint information through the PSG: 17 | 18 | ```fsharp 19 | // Generated from CMSIS GPIO header 20 | let gpioPeripheralQuotation: Expr = <@ 21 | { Name = "GPIO" 22 | Instances = Map.ofList [ 23 | ("GPIOA", 0x48000000un) 24 | ("GPIOB", 0x48000400un) 25 | ("GPIOC", 0x48000800un) 26 | ] 27 | Layout = { 28 | Size = 0x400 29 | Alignment = 4 30 | Fields = [ 31 | { Name = "MODER"; Offset = 0x00; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Mode register" } 32 | { Name = "IDR"; Offset = 0x10; Type = U32; Access = ReadOnly; BitFields = None; Documentation = Some "Input data register" } 33 | { Name = "BSRR"; Offset = 0x18; Type = U32; Access = WriteOnly; BitFields = None; Documentation = Some "Bit set/reset register" } 34 | ] 35 | } 36 | MemoryRegion = Peripheral 37 | } 38 | @> 39 | ``` 40 | 41 | ### 2. Active Patterns 42 | 43 | Compositional recognition patterns for PSG traversal: 44 | 45 | ```fsharp 46 | // Generated from HAL function signatures 47 | let (|GpioWritePin|_|) (node: PSGNode) : (string * int * bool) option = 48 | match node with 49 | | CallToExtern "HAL_GPIO_WritePin" [gpio; pin; state] -> 50 | Some (extractGpioInstance gpio, extractPinNum pin, extractState state) 51 | | _ -> None 52 | 53 | let (|GpioReadPin|_|) (node: PSGNode) : (string * int) option = 54 | match node with 55 | | CallToExtern "HAL_GPIO_ReadPin" [gpio; pin] -> 56 | Some (extractGpioInstance gpio, extractPinNum pin) 57 | | _ -> None 58 | 59 | // Composed patterns 60 | let (|PeripheralAccess|_|) node = 61 | match node with 62 | | GpioWritePin info -> Some (GpioWrite info) 63 | | GpioReadPin info -> Some (GpioRead info) 64 | | UsartTransmit info -> Some (UsartTx info) 65 | | _ -> None 66 | ``` 67 | 68 | ### 3. MemoryModel Record 69 | 70 | Integration surface for fsnative nanopass pipeline: 71 | 72 | ```fsharp 73 | // Generated for each target family 74 | let stm32l5MemoryModel: MemoryModel = { 75 | TargetFamily = "STM32L5" 76 | PeripheralDescriptors = [ 77 | gpioPeripheralQuotation 78 | usartPeripheralQuotation 79 | spiPeripheralQuotation 80 | i2cPeripheralQuotation 81 | ] 82 | RegisterConstraints = [ 83 | gpioAccessConstraints 84 | usartAccessConstraints 85 | ] 86 | Regions = <@ [ 87 | { Name = "Flash"; Start = 0x08000000un; Size = 512 * 1024; Kind = Flash } 88 | { Name = "SRAM1"; Start = 0x20000000un; Size = 256 * 1024; Kind = SRAM } 89 | { Name = "Peripherals"; Start = 0x40000000un; Size = 0x20000000; Kind = Peripheral } 90 | ] @> 91 | Recognize = recognizeSTM32L5MemoryOperation 92 | CacheTopology = None 93 | CoherencyModel = None 94 | } 95 | ``` 96 | 97 | ### 4. High-Level F# API (Optional) 98 | 99 | Clean user-facing API that hides descriptor complexity: 100 | 101 | ```fsharp 102 | // Generated Fidelity.STM32L5.GPIO module 103 | module GPIO = 104 | let inline writePin port pin state = 105 | halGpioWritePin (getPortBase port) pin state 106 | 107 | let inline readPin port pin = 108 | halGpioReadPin (getPortBase port) pin 109 | ``` 110 | 111 | ## Generation Pipeline 112 | 113 | ``` 114 | C/C++ Header 115 | ↓ XParsec parsing 116 | Parsed AST (structs, functions, macros) 117 | ↓ Type mapping 118 | BAREWire descriptor instances 119 | ↓ Quotation generation 120 | Expr quotations 121 | ↓ Pattern generation 122 | Active patterns for each function 123 | ↓ Model assembly 124 | MemoryModel record 125 | ↓ Output 126 | ├── Quotations.fs 127 | ├── Patterns.fs 128 | ├── MemoryModel.fs 129 | └── API.fs (optional high-level) 130 | ``` 131 | 132 | ## CMSIS Qualifier Mapping 133 | 134 | | CMSIS | C Definition | Farscape Output | 135 | |-------|--------------|-----------------| 136 | | `__I` | `volatile const` | `Access = ReadOnly` in quotation | 137 | | `__O` | `volatile` | `Access = WriteOnly` in quotation | 138 | | `__IO` | `volatile` | `Access = ReadWrite` in quotation | 139 | 140 | These map directly to `AccessKind` in generated quotations and inform active pattern validation. 141 | 142 | ## Canonical Reference 143 | 144 | See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md` for the complete four-component architecture. 145 | -------------------------------------------------------------------------------- /.serena/.serena/memories/quotation_generation_architecture.md: -------------------------------------------------------------------------------- 1 | # Farscape Quotation Generation Architecture 2 | 3 | ## Primary Mission 4 | 5 | Farscape transforms C/C++ headers into **F# quotations and active patterns** for the Fidelity nanopass pipeline. 6 | 7 | This is NOT traditional FFI generation. Farscape produces: 8 | 1. `Expr` quotations encoding memory layout 9 | 2. Active patterns for PSG node recognition 10 | 3. `MemoryModel` records for fsnative integration 11 | 12 | ## The Four Outputs 13 | 14 | ### 1. Quotations (Expr) 15 | 16 | Quotations carry memory constraint information through the PSG: 17 | 18 | ```fsharp 19 | // Generated from CMSIS GPIO header 20 | let gpioPeripheralQuotation: Expr = <@ 21 | { Name = "GPIO" 22 | Instances = Map.ofList [ 23 | ("GPIOA", 0x48000000un) 24 | ("GPIOB", 0x48000400un) 25 | ("GPIOC", 0x48000800un) 26 | ] 27 | Layout = { 28 | Size = 0x400 29 | Alignment = 4 30 | Fields = [ 31 | { Name = "MODER"; Offset = 0x00; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Mode register" } 32 | { Name = "IDR"; Offset = 0x10; Type = U32; Access = ReadOnly; BitFields = None; Documentation = Some "Input data register" } 33 | { Name = "BSRR"; Offset = 0x18; Type = U32; Access = WriteOnly; BitFields = None; Documentation = Some "Bit set/reset register" } 34 | ] 35 | } 36 | MemoryRegion = Peripheral 37 | } 38 | @> 39 | ``` 40 | 41 | ### 2. Active Patterns 42 | 43 | Compositional recognition patterns for PSG traversal: 44 | 45 | ```fsharp 46 | // Generated from HAL function signatures 47 | let (|GpioWritePin|_|) (node: PSGNode) : (string * int * bool) option = 48 | match node with 49 | | CallToExtern "HAL_GPIO_WritePin" [gpio; pin; state] -> 50 | Some (extractGpioInstance gpio, extractPinNum pin, extractState state) 51 | | _ -> None 52 | 53 | let (|GpioReadPin|_|) (node: PSGNode) : (string * int) option = 54 | match node with 55 | | CallToExtern "HAL_GPIO_ReadPin" [gpio; pin] -> 56 | Some (extractGpioInstance gpio, extractPinNum pin) 57 | | _ -> None 58 | 59 | // Composed patterns 60 | let (|PeripheralAccess|_|) node = 61 | match node with 62 | | GpioWritePin info -> Some (GpioWrite info) 63 | | GpioReadPin info -> Some (GpioRead info) 64 | | UsartTransmit info -> Some (UsartTx info) 65 | | _ -> None 66 | ``` 67 | 68 | ### 3. MemoryModel Record 69 | 70 | Integration surface for fsnative nanopass pipeline: 71 | 72 | ```fsharp 73 | // Generated for each target family 74 | let stm32l5MemoryModel: MemoryModel = { 75 | TargetFamily = "STM32L5" 76 | PeripheralDescriptors = [ 77 | gpioPeripheralQuotation 78 | usartPeripheralQuotation 79 | spiPeripheralQuotation 80 | i2cPeripheralQuotation 81 | ] 82 | RegisterConstraints = [ 83 | gpioAccessConstraints 84 | usartAccessConstraints 85 | ] 86 | Regions = <@ [ 87 | { Name = "Flash"; Start = 0x08000000un; Size = 512 * 1024; Kind = Flash } 88 | { Name = "SRAM1"; Start = 0x20000000un; Size = 256 * 1024; Kind = SRAM } 89 | { Name = "Peripherals"; Start = 0x40000000un; Size = 0x20000000; Kind = Peripheral } 90 | ] @> 91 | Recognize = recognizeSTM32L5MemoryOperation 92 | CacheTopology = None 93 | CoherencyModel = None 94 | } 95 | ``` 96 | 97 | ### 4. High-Level F# API (Optional) 98 | 99 | Clean user-facing API that hides descriptor complexity: 100 | 101 | ```fsharp 102 | // Generated Fidelity.STM32L5.GPIO module 103 | module GPIO = 104 | let inline writePin port pin state = 105 | halGpioWritePin (getPortBase port) pin state 106 | 107 | let inline readPin port pin = 108 | halGpioReadPin (getPortBase port) pin 109 | ``` 110 | 111 | ## Generation Pipeline 112 | 113 | ``` 114 | C/C++ Header 115 | ↓ XParsec parsing 116 | Parsed AST (structs, functions, macros) 117 | ↓ Type mapping 118 | BAREWire descriptor instances 119 | ↓ Quotation generation 120 | Expr quotations 121 | ↓ Pattern generation 122 | Active patterns for each function 123 | ↓ Model assembly 124 | MemoryModel record 125 | ↓ Output 126 | ├── Quotations.fs 127 | ├── Patterns.fs 128 | ├── MemoryModel.fs 129 | └── API.fs (optional high-level) 130 | ``` 131 | 132 | ## CMSIS Qualifier Mapping 133 | 134 | | CMSIS | C Definition | Farscape Output | 135 | |-------|--------------|-----------------| 136 | | `__I` | `volatile const` | `Access = ReadOnly` in quotation | 137 | | `__O` | `volatile` | `Access = WriteOnly` in quotation | 138 | | `__IO` | `volatile` | `Access = ReadWrite` in quotation | 139 | 140 | These map directly to `AccessKind` in generated quotations and inform active pattern validation. 141 | 142 | ## Canonical Reference 143 | 144 | See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md` for the complete four-component architecture. 145 | -------------------------------------------------------------------------------- /.serena/memories/fsnative_integration_contract.md: -------------------------------------------------------------------------------- 1 | # Farscape-fsnative Integration Contract 2 | 3 | ## The Integration Surface 4 | 5 | Farscape generates output that fsnative's nanopass pipeline consumes. The integration point is the `MemoryModel` record type. 6 | 7 | ## What Farscape Provides 8 | 9 | ### 1. MemoryModel Record 10 | 11 | ```fsharp 12 | type MemoryModel = { 13 | /// Target family identifier (e.g., "STM32L5", "NRF52") 14 | TargetFamily: string 15 | 16 | /// Quotations encoding peripheral memory layout 17 | PeripheralDescriptors: Expr list 18 | 19 | /// Quotations encoding access constraints 20 | RegisterConstraints: Expr list 21 | 22 | /// Quotation encoding memory regions 23 | Regions: Expr 24 | 25 | /// Active pattern function for PSG recognition 26 | Recognize: PSGNode -> MemoryOperation option 27 | 28 | /// Optional cache topology (for optimization) 29 | CacheTopology: Expr option 30 | 31 | /// Optional coherency model (for multi-core) 32 | CoherencyModel: Expr option 33 | } 34 | ``` 35 | 36 | ### 2. Quotations for Nanopass Consumption 37 | 38 | Quotations carry information that fsnative nanopasses can inspect: 39 | 40 | ```fsharp 41 | // Farscape generates 42 | let gpioQuotation: Expr = <@ 43 | { Name = "GPIO" 44 | Instances = Map.ofList [("GPIOA", 0x48000000un)] 45 | Layout = { Size = 0x400; Alignment = 4; Fields = [...] } 46 | MemoryRegion = Peripheral } 47 | @> 48 | 49 | // fsnative nanopass can decompose 50 | match gpioQuotation with 51 | | <@ { Name = name; MemoryRegion = Peripheral; _ } @> -> 52 | // Mark all access as volatile 53 | | <@ { MemoryRegion = Flash; _ } @> -> 54 | // Emit read-only constraints 55 | ``` 56 | 57 | ### 3. Active Patterns for Recognition 58 | 59 | fsnative uses the `Recognize` function during PSG traversal: 60 | 61 | ```fsharp 62 | // In fsnative nanopass 63 | let enrichMemorySemantics (model: MemoryModel) (node: PSGNode) = 64 | match model.Recognize node with 65 | | Some (PeripheralOp op) -> 66 | // Attach volatile semantics 67 | node |> withVolatile |> withAccessKind op.Access 68 | | Some (DmaOp op) -> 69 | // Attach DMA semantics 70 | node |> withDmaMarker op.Channel 71 | | None -> 72 | // Normal memory operation 73 | node 74 | ``` 75 | 76 | ## What fsnative Expects 77 | 78 | ### Type Definitions in Scope 79 | 80 | fsnative expects these types to be defined (by BAREWire): 81 | 82 | ```fsharp 83 | type PeripheralDescriptor = { ... } 84 | type FieldDescriptor = { ... } 85 | type AccessKind = ReadOnly | WriteOnly | ReadWrite 86 | type MemoryRegionKind = Flash | SRAM | Peripheral | SystemControl | DMA | CCM 87 | type RegisterConstraint = { ... } 88 | type RegionDescriptor = { ... } 89 | ``` 90 | 91 | ### PSGNode Pattern Matching 92 | 93 | The `Recognize` function receives `PSGNode` and must handle: 94 | 95 | ```fsharp 96 | type MemoryOperation = 97 | | PeripheralOp of PeripheralAccessInfo 98 | | DmaOp of DmaOperationInfo 99 | | SystemOp of SystemControlInfo 100 | | FlashRead of FlashReadInfo 101 | ``` 102 | 103 | ### Quotation Structure 104 | 105 | Quotations must be decomposable. Use record literals, not function calls: 106 | 107 | ```fsharp 108 | // ✅ Decomposable 109 | <@ { Name = "GPIO"; MemoryRegion = Peripheral } @> 110 | 111 | // ❌ Not decomposable 112 | <@ createPeripheral "GPIO" Peripheral @> 113 | ``` 114 | 115 | ## The Dependency Chain 116 | 117 | ``` 118 | BAREWire (types) ← Farscape (quotations) ← fsnative (consumption) 119 | ``` 120 | 121 | 1. **BAREWire** provides type definitions 122 | 2. **Farscape** generates quotations using those types 123 | 3. **fsnative** pattern-matches on quotations in nanopasses 124 | 125 | ## Registration Flow 126 | 127 | ```fsharp 128 | // Farscape generates a registration module 129 | module STM32L5.Registration = 130 | let memoryModel: MemoryModel = { 131 | TargetFamily = "STM32L5" 132 | PeripheralDescriptors = [gpioQuotation; usartQuotation; ...] 133 | RegisterConstraints = [accessConstraints] 134 | Regions = regionQuotation 135 | Recognize = recognizeSTM32L5Operation 136 | CacheTopology = None 137 | CoherencyModel = None 138 | } 139 | 140 | // fsnative loads at compile time 141 | let models = [ 142 | STM32L5.Registration.memoryModel 143 | NRF52.Registration.memoryModel 144 | // ... 145 | ] 146 | ``` 147 | 148 | ## Error Reporting 149 | 150 | Farscape-generated patterns should provide good error context: 151 | 152 | ```fsharp 153 | let (|WriteToReadOnly|_|) node = 154 | match node with 155 | | RegisterWrite { Register = reg } when reg.Access = ReadOnly -> 156 | Some { 157 | Register = reg.Name 158 | Peripheral = reg.Peripheral 159 | Message = $"Cannot write to read-only register {reg.Name}" 160 | Suggestion = "Use a read-write register or check hardware documentation" 161 | } 162 | | _ -> None 163 | ``` 164 | 165 | ## Canonical Reference 166 | 167 | See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md` for the complete integration architecture. 168 | -------------------------------------------------------------------------------- /.serena/.serena/memories/fsnative_integration_contract.md: -------------------------------------------------------------------------------- 1 | # Farscape-fsnative Integration Contract 2 | 3 | ## The Integration Surface 4 | 5 | Farscape generates output that fsnative's nanopass pipeline consumes. The integration point is the `MemoryModel` record type. 6 | 7 | ## What Farscape Provides 8 | 9 | ### 1. MemoryModel Record 10 | 11 | ```fsharp 12 | type MemoryModel = { 13 | /// Target family identifier (e.g., "STM32L5", "NRF52") 14 | TargetFamily: string 15 | 16 | /// Quotations encoding peripheral memory layout 17 | PeripheralDescriptors: Expr list 18 | 19 | /// Quotations encoding access constraints 20 | RegisterConstraints: Expr list 21 | 22 | /// Quotation encoding memory regions 23 | Regions: Expr 24 | 25 | /// Active pattern function for PSG recognition 26 | Recognize: PSGNode -> MemoryOperation option 27 | 28 | /// Optional cache topology (for optimization) 29 | CacheTopology: Expr option 30 | 31 | /// Optional coherency model (for multi-core) 32 | CoherencyModel: Expr option 33 | } 34 | ``` 35 | 36 | ### 2. Quotations for Nanopass Consumption 37 | 38 | Quotations carry information that fsnative nanopasses can inspect: 39 | 40 | ```fsharp 41 | // Farscape generates 42 | let gpioQuotation: Expr = <@ 43 | { Name = "GPIO" 44 | Instances = Map.ofList [("GPIOA", 0x48000000un)] 45 | Layout = { Size = 0x400; Alignment = 4; Fields = [...] } 46 | MemoryRegion = Peripheral } 47 | @> 48 | 49 | // fsnative nanopass can decompose 50 | match gpioQuotation with 51 | | <@ { Name = name; MemoryRegion = Peripheral; _ } @> -> 52 | // Mark all access as volatile 53 | | <@ { MemoryRegion = Flash; _ } @> -> 54 | // Emit read-only constraints 55 | ``` 56 | 57 | ### 3. Active Patterns for Recognition 58 | 59 | fsnative uses the `Recognize` function during PSG traversal: 60 | 61 | ```fsharp 62 | // In fsnative nanopass 63 | let enrichMemorySemantics (model: MemoryModel) (node: PSGNode) = 64 | match model.Recognize node with 65 | | Some (PeripheralOp op) -> 66 | // Attach volatile semantics 67 | node |> withVolatile |> withAccessKind op.Access 68 | | Some (DmaOp op) -> 69 | // Attach DMA semantics 70 | node |> withDmaMarker op.Channel 71 | | None -> 72 | // Normal memory operation 73 | node 74 | ``` 75 | 76 | ## What fsnative Expects 77 | 78 | ### Type Definitions in Scope 79 | 80 | fsnative expects these types to be defined (by BAREWire): 81 | 82 | ```fsharp 83 | type PeripheralDescriptor = { ... } 84 | type FieldDescriptor = { ... } 85 | type AccessKind = ReadOnly | WriteOnly | ReadWrite 86 | type MemoryRegionKind = Flash | SRAM | Peripheral | SystemControl | DMA | CCM 87 | type RegisterConstraint = { ... } 88 | type RegionDescriptor = { ... } 89 | ``` 90 | 91 | ### PSGNode Pattern Matching 92 | 93 | The `Recognize` function receives `PSGNode` and must handle: 94 | 95 | ```fsharp 96 | type MemoryOperation = 97 | | PeripheralOp of PeripheralAccessInfo 98 | | DmaOp of DmaOperationInfo 99 | | SystemOp of SystemControlInfo 100 | | FlashRead of FlashReadInfo 101 | ``` 102 | 103 | ### Quotation Structure 104 | 105 | Quotations must be decomposable. Use record literals, not function calls: 106 | 107 | ```fsharp 108 | // ✅ Decomposable 109 | <@ { Name = "GPIO"; MemoryRegion = Peripheral } @> 110 | 111 | // ❌ Not decomposable 112 | <@ createPeripheral "GPIO" Peripheral @> 113 | ``` 114 | 115 | ## The Dependency Chain 116 | 117 | ``` 118 | BAREWire (types) ← Farscape (quotations) ← fsnative (consumption) 119 | ``` 120 | 121 | 1. **BAREWire** provides type definitions 122 | 2. **Farscape** generates quotations using those types 123 | 3. **fsnative** pattern-matches on quotations in nanopasses 124 | 125 | ## Registration Flow 126 | 127 | ```fsharp 128 | // Farscape generates a registration module 129 | module STM32L5.Registration = 130 | let memoryModel: MemoryModel = { 131 | TargetFamily = "STM32L5" 132 | PeripheralDescriptors = [gpioQuotation; usartQuotation; ...] 133 | RegisterConstraints = [accessConstraints] 134 | Regions = regionQuotation 135 | Recognize = recognizeSTM32L5Operation 136 | CacheTopology = None 137 | CoherencyModel = None 138 | } 139 | 140 | // fsnative loads at compile time 141 | let models = [ 142 | STM32L5.Registration.memoryModel 143 | NRF52.Registration.memoryModel 144 | // ... 145 | ] 146 | ``` 147 | 148 | ## Error Reporting 149 | 150 | Farscape-generated patterns should provide good error context: 151 | 152 | ```fsharp 153 | let (|WriteToReadOnly|_|) node = 154 | match node with 155 | | RegisterWrite { Register = reg } when reg.Access = ReadOnly -> 156 | Some { 157 | Register = reg.Name 158 | Peripheral = reg.Peripheral 159 | Message = $"Cannot write to read-only register {reg.Name}" 160 | Suggestion = "Use a read-write register or check hardware documentation" 161 | } 162 | | _ -> None 163 | ``` 164 | 165 | ## Canonical Reference 166 | 167 | See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md` for the complete integration architecture. 168 | -------------------------------------------------------------------------------- /.serena/project.yml: -------------------------------------------------------------------------------- 1 | # list of languages for which language servers are started; choose from: 2 | # al bash clojure cpp csharp csharp_omnisharp 3 | # dart elixir elm erlang fortran go 4 | # haskell java julia kotlin lua markdown 5 | # nix perl php python python_jedi r 6 | # rego ruby ruby_solargraph rust scala swift 7 | # terraform typescript typescript_vts yaml zig 8 | # Note: 9 | # - For C, use cpp 10 | # - For JavaScript, use typescript 11 | # Special requirements: 12 | # - csharp: Requires the presence of a .sln file in the project folder. 13 | # When using multiple languages, the first language server that supports a given file will be used for that file. 14 | # The first language is the default language and the respective language server will be used as a fallback. 15 | # Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. 16 | languages: 17 | - fsharp 18 | 19 | # the encoding used by text files in the project 20 | # For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings 21 | encoding: "utf-8" 22 | 23 | # whether to use the project's gitignore file to ignore files 24 | # Added on 2025-04-07 25 | ignore_all_files_in_gitignore: true 26 | 27 | # list of additional paths to ignore 28 | # same syntax as gitignore, so you can use * and ** 29 | # Was previously called `ignored_dirs`, please update your config if you are using that. 30 | # Added (renamed) on 2025-04-07 31 | ignored_paths: [] 32 | 33 | # whether the project is in read-only mode 34 | # If set to true, all editing tools will be disabled and attempts to use them will result in an error 35 | # Added on 2025-04-18 36 | read_only: false 37 | 38 | # list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. 39 | # Below is the complete list of tools for convenience. 40 | # To make sure you have the latest list of tools, and to view their descriptions, 41 | # execute `uv run scripts/print_tool_overview.py`. 42 | # 43 | # * `activate_project`: Activates a project by name. 44 | # * `check_onboarding_performed`: Checks whether project onboarding was already performed. 45 | # * `create_text_file`: Creates/overwrites a file in the project directory. 46 | # * `delete_lines`: Deletes a range of lines within a file. 47 | # * `delete_memory`: Deletes a memory from Serena's project-specific memory store. 48 | # * `execute_shell_command`: Executes a shell command. 49 | # * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. 50 | # * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). 51 | # * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). 52 | # * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. 53 | # * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. 54 | # * `initial_instructions`: Gets the initial instructions for the current project. 55 | # Should only be used in settings where the system prompt cannot be set, 56 | # e.g. in clients you have no control over, like Claude Desktop. 57 | # * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. 58 | # * `insert_at_line`: Inserts content at a given line in a file. 59 | # * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. 60 | # * `list_dir`: Lists files and directories in the given directory (optionally with recursion). 61 | # * `list_memories`: Lists memories in Serena's project-specific memory store. 62 | # * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). 63 | # * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). 64 | # * `read_file`: Reads a file within the project directory. 65 | # * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. 66 | # * `remove_project`: Removes a project from the Serena configuration. 67 | # * `replace_lines`: Replaces a range of lines within a file with new content. 68 | # * `replace_symbol_body`: Replaces the full definition of a symbol. 69 | # * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. 70 | # * `search_for_pattern`: Performs a search for a pattern in the project. 71 | # * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. 72 | # * `switch_modes`: Activates modes by providing a list of their names 73 | # * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. 74 | # * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. 75 | # * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. 76 | # * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. 77 | excluded_tools: [] 78 | 79 | # initial prompt for the project. It will always be given to the LLM upon activating the project 80 | # (contrary to the memories, which are loaded on demand). 81 | initial_prompt: "" 82 | 83 | project_name: "Farscape" 84 | included_optional_tools: [] 85 | -------------------------------------------------------------------------------- /.serena/.serena/project.yml: -------------------------------------------------------------------------------- 1 | # list of languages for which language servers are started; choose from: 2 | # al bash clojure cpp csharp csharp_omnisharp 3 | # dart elixir elm erlang fortran go 4 | # haskell java julia kotlin lua markdown 5 | # nix perl php python python_jedi r 6 | # rego ruby ruby_solargraph rust scala swift 7 | # terraform typescript typescript_vts yaml zig 8 | # Note: 9 | # - For C, use cpp 10 | # - For JavaScript, use typescript 11 | # Special requirements: 12 | # - csharp: Requires the presence of a .sln file in the project folder. 13 | # When using multiple languages, the first language server that supports a given file will be used for that file. 14 | # The first language is the default language and the respective language server will be used as a fallback. 15 | # Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. 16 | languages: 17 | - fsharp 18 | 19 | # the encoding used by text files in the project 20 | # For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings 21 | encoding: "utf-8" 22 | 23 | # whether to use the project's gitignore file to ignore files 24 | # Added on 2025-04-07 25 | ignore_all_files_in_gitignore: true 26 | 27 | # list of additional paths to ignore 28 | # same syntax as gitignore, so you can use * and ** 29 | # Was previously called `ignored_dirs`, please update your config if you are using that. 30 | # Added (renamed) on 2025-04-07 31 | ignored_paths: [] 32 | 33 | # whether the project is in read-only mode 34 | # If set to true, all editing tools will be disabled and attempts to use them will result in an error 35 | # Added on 2025-04-18 36 | read_only: false 37 | 38 | # list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. 39 | # Below is the complete list of tools for convenience. 40 | # To make sure you have the latest list of tools, and to view their descriptions, 41 | # execute `uv run scripts/print_tool_overview.py`. 42 | # 43 | # * `activate_project`: Activates a project by name. 44 | # * `check_onboarding_performed`: Checks whether project onboarding was already performed. 45 | # * `create_text_file`: Creates/overwrites a file in the project directory. 46 | # * `delete_lines`: Deletes a range of lines within a file. 47 | # * `delete_memory`: Deletes a memory from Serena's project-specific memory store. 48 | # * `execute_shell_command`: Executes a shell command. 49 | # * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. 50 | # * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). 51 | # * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). 52 | # * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. 53 | # * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. 54 | # * `initial_instructions`: Gets the initial instructions for the current project. 55 | # Should only be used in settings where the system prompt cannot be set, 56 | # e.g. in clients you have no control over, like Claude Desktop. 57 | # * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. 58 | # * `insert_at_line`: Inserts content at a given line in a file. 59 | # * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. 60 | # * `list_dir`: Lists files and directories in the given directory (optionally with recursion). 61 | # * `list_memories`: Lists memories in Serena's project-specific memory store. 62 | # * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). 63 | # * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). 64 | # * `read_file`: Reads a file within the project directory. 65 | # * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. 66 | # * `remove_project`: Removes a project from the Serena configuration. 67 | # * `replace_lines`: Replaces a range of lines within a file with new content. 68 | # * `replace_symbol_body`: Replaces the full definition of a symbol. 69 | # * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. 70 | # * `search_for_pattern`: Performs a search for a pattern in the project. 71 | # * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. 72 | # * `switch_modes`: Activates modes by providing a list of their names 73 | # * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. 74 | # * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. 75 | # * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. 76 | # * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. 77 | excluded_tools: [] 78 | 79 | # initial prompt for the project. It will always be given to the LLM upon activating the project 80 | # (contrary to the memories, which are loaded on demand). 81 | initial_prompt: "" 82 | 83 | project_name: "Farscape" 84 | included_optional_tools: [] 85 | -------------------------------------------------------------------------------- /.serena/.serena/memories/native_binding_generation.md: -------------------------------------------------------------------------------- 1 | # Farscape's Role in Native Library Binding 2 | 3 | > **Architecture Update (December 2024)**: Farscape now generates **quotation-based output** with active patterns, 4 | > not just raw P/Invoke declarations. See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md`. 5 | 6 | ## Position in the Architecture 7 | 8 | Farscape is the **binding generator** - it transforms C/C++ headers into F# source code with quotations: 9 | 10 | ``` 11 | C Headers → Farscape → F# Source (Fidelity.[Target] + BAREWire.[Target] + Externs) 12 | ``` 13 | 14 | Farscape runs at **generation time**, before Firefly compilation. 15 | 16 | ## Farscape's Quotation-Based Outputs 17 | 18 | ### 1. Expr Quotations 19 | Memory layout information as quotations for nanopass consumption: 20 | 21 | ```fsharp 22 | let gpioQuotation: Expr = <@ 23 | { Name = "GPIO" 24 | Instances = Map.ofList [("GPIOA", 0x48000000un)] 25 | Layout = { Size = 0x400; Alignment = 4; Fields = gpioFields } 26 | MemoryRegion = Peripheral } 27 | @> 28 | ``` 29 | 30 | ### 2. Active Patterns for Recognition 31 | PSG pattern matching for Alex consumption: 32 | 33 | ```fsharp 34 | let (|GpioWritePin|_|) (node: PSGNode) : (string * int * uint32) option = ... 35 | let (|PeripheralAccess|_|) (node: PSGNode) : PeripheralAccessInfo option = ... 36 | ``` 37 | 38 | ### 3. MemoryModel Record 39 | Integration surface using pure F# record (not interface): 40 | 41 | ```fsharp 42 | let stm32l5MemoryModel: MemoryModel = { 43 | TargetFamily = "STM32L5" 44 | PeripheralDescriptors = [gpioQuotation; usartQuotation] 45 | RegisterConstraints = [accessConstraints] 46 | Regions = regionQuotation 47 | Recognize = recognizeMemoryOperation 48 | CacheTopology = None 49 | CoherencyModel = None 50 | } 51 | ``` 52 | 53 | ### 4. Fidelity.[Target] - High-Level F# API 54 | Developer-facing library with idiomatic F# types: 55 | 56 | ```fsharp 57 | // Fidelity.STM32L5/GPIO.fs 58 | module Fidelity.STM32L5.GPIO 59 | 60 | type Port = GPIOA | GPIOB | GPIOC | ... 61 | type Mode = Input | Output | Alternate | Analog 62 | 63 | let init (port: Port) (pin: int) (mode: Mode) : Result = ... 64 | let inline writePin (port: Port) (pin: int) (state: bool) : unit = ... 65 | ``` 66 | 67 | ### 2. BAREWire.[Target] - Memory Descriptors 68 | Compile-time hardware memory map using BAREWire.Core types: 69 | 70 | ```fsharp 71 | // BAREWire.STM32L5/Descriptors.fs 72 | let GPIO : PeripheralDescriptor = { 73 | Name = "GPIO" 74 | Region = MemoryRegionKind.Peripheral 75 | Instances = Map.ofList [("GPIOA", 0x48000000un); ...] 76 | Registers = [ 77 | { Name = "MODER"; Offset = 0x00; Width = 32; Access = ReadWrite; ... } 78 | { Name = "IDR"; Offset = 0x10; Width = 32; Access = ReadOnly; ... } 79 | { Name = "BSRR"; Offset = 0x18; Width = 32; Access = WriteOnly; ... } 80 | ] 81 | } 82 | ``` 83 | 84 | ### 3. Extern Declarations - Library Function Bindings 85 | F# externs for pre-compiled C library functions: 86 | 87 | ```fsharp 88 | // Fidelity.STM32L5/HAL.fs 89 | [] 90 | extern HAL_StatusTypeDef HAL_GPIO_Init(nativeint GPIOx, nativeint GPIO_Init) 91 | 92 | [] 93 | extern HAL_StatusTypeDef HAL_UART_Transmit(nativeint huart, nativeint pData, uint16 Size, uint32 Timeout) 94 | ``` 95 | 96 | ## What Farscape Parses 97 | 98 | From C headers, Farscape extracts: 99 | 100 | | C Construct | F# Output | 101 | |-------------|-----------| 102 | | `typedef struct {...} XXX_TypeDef` | PeripheralDescriptor + F# record | 103 | | `__IO uint32_t FIELD` | Register with Access = ReadWrite | 104 | | `__I uint32_t FIELD` | Register with Access = ReadOnly | 105 | | `__O uint32_t FIELD` | Register with Access = WriteOnly | 106 | | `#define XXX_BASE (addr)` | Instance address in Instances map | 107 | | `#define XXX_Pos (n)` | BitField position | 108 | | `#define XXX_Msk (m)` | BitField width (computed from mask) | 109 | | `typedef enum {...}` | F# discriminated union | 110 | | `RetType FuncName(params)` | DllImport extern declaration | 111 | 112 | ## Key CMSIS Patterns 113 | 114 | ```c 115 | // Access qualifiers → AccessKind 116 | #define __IO volatile // ReadWrite 117 | #define __I volatile const // ReadOnly 118 | #define __O volatile // WriteOnly 119 | 120 | // Bit field macros → BitFieldDescriptor 121 | #define USART_CR1_UE_Pos (0U) 122 | #define USART_CR1_UE_Msk (0x1UL << USART_CR1_UE_Pos) 123 | 124 | // Peripheral instance → address in Instances map 125 | #define GPIOA_BASE (0x48000000UL) 126 | #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) 127 | ``` 128 | 129 | ## Link-Time Consideration 130 | 131 | The extern declarations reference a **library name** (e.g., `"stm32l5xx_hal"`). 132 | 133 | At link time, the linker must find `libstm32l5xx_hal.a` containing: 134 | - `HAL_GPIO_Init` 135 | - `HAL_UART_Transmit` 136 | - etc. 137 | 138 | Farscape doesn't produce this library - it's pre-compiled by the vendor (STMicroelectronics). Farscape only produces the F# bindings that reference it. 139 | 140 | ## What Farscape Does NOT Do 141 | 142 | - Generate MLIR or LLVM code 143 | - Know about Alex's internal patterns 144 | - Make platform-specific code generation decisions 145 | - Compile the HAL library itself 146 | 147 | ## Relationship to Other Projects 148 | 149 | | Project | Relationship | 150 | |---------|--------------| 151 | | **BAREWire** | Farscape uses BAREWire.Core types; generates BAREWire.[Target] | 152 | | **Firefly** | Firefly compiles Farscape's output; Alex handles externs | 153 | | **Alloy** | Generated Fidelity.[Target] may use Alloy types | 154 | 155 | ## Canonical Document 156 | 157 | See Firefly `/docs/Native_Library_Binding_Architecture.md` for the complete binding architecture. 158 | -------------------------------------------------------------------------------- /src/Farscape.Cli/Program.fs: -------------------------------------------------------------------------------- 1 | module Farscape.Cli.Program 2 | 3 | open System 4 | open System.IO 5 | open Farscape.Core 6 | open FSharp.SystemCommandLine 7 | open System.CommandLine.Invocation 8 | open Farscape.Core.BindingGenerator 9 | 10 | let printLine (text: string) = 11 | Console.WriteLine(text) 12 | 13 | let printColorLine (text: string) (color: ConsoleColor) = 14 | let originalColor = Console.ForegroundColor 15 | Console.ForegroundColor <- color 16 | Console.WriteLine(text) 17 | Console.ForegroundColor <- originalColor 18 | 19 | let printHeader (text: string) = 20 | let width = Math.Min(Console.WindowWidth, 80) 21 | let line = new String('=', width) 22 | printColorLine line ConsoleColor.Cyan 23 | printColorLine text ConsoleColor.Cyan 24 | printColorLine line ConsoleColor.Cyan 25 | 26 | let showHeader () = 27 | printHeader "Farscape: F# Native Library Binding Generator" 28 | 29 | let showConfiguration (options: GenerationOptions) = 30 | printHeader "Configuration" 31 | printLine "" 32 | 33 | printColorLine "Header File:" ConsoleColor.Yellow 34 | printLine $" {options.HeaderFile}" 35 | 36 | printColorLine "Library Name:" ConsoleColor.Yellow 37 | printLine $" {options.LibraryName}" 38 | 39 | printColorLine "Output Directory:" ConsoleColor.Yellow 40 | printLine $" {options.OutputDirectory}" 41 | 42 | printColorLine "Namespace:" ConsoleColor.Yellow 43 | printLine $" {options.Namespace}" 44 | 45 | printColorLine "Include Paths:" ConsoleColor.Yellow 46 | if options.IncludePaths = [] then 47 | printLine " None" 48 | else 49 | options.IncludePaths 50 | |> List.iter (fun path -> printLine $" {path}") 51 | 52 | printColorLine "Defines:" ConsoleColor.Yellow 53 | if options.Defines = [] then 54 | printLine " None" 55 | else 56 | options.Defines 57 | |> List.iter (fun d -> printLine $" {d}") 58 | 59 | printLine "" 60 | 61 | let runGeneration (options: GenerationOptions) : Result = 62 | // Show generating message 63 | printHeader "Generating F# bindings..." 64 | printLine "" 65 | 66 | match BindingGenerator.generateBindings options with 67 | | Error errorMsg -> 68 | Error errorMsg 69 | | Ok result -> 70 | printLine "" 71 | printColorLine "Generation Complete" ConsoleColor.Green 72 | printLine "" 73 | printColorLine $"Parsed {result.DeclarationCount} declarations from header" ConsoleColor.White 74 | printColorLine $"Output: F# bindings generated in {options.OutputDirectory}" ConsoleColor.Cyan 75 | printLine "" 76 | Ok result 77 | 78 | let showNextSteps (options: GenerationOptions) = 79 | // Show next steps 80 | printHeader "Next Steps" 81 | printLine "" 82 | 83 | printLine "How to use the generated bindings:" 84 | printLine "" 85 | 86 | printLine "Build the project:" 87 | printLine $" cd {options.OutputDirectory}" 88 | printLine " dotnet build" 89 | printLine "" 90 | 91 | printLine "Use in your own project:" 92 | printLine $" Add a reference to {options.LibraryName}.dll" 93 | printLine $" open {options.Namespace}" 94 | printLine "" 95 | 96 | let showError (message: string) = 97 | printColorLine $"Error: {message}" ConsoleColor.Red 98 | printLine "" 99 | 100 | let generateCommand = 101 | let header = 102 | Input.Option(["-h"; "--header"], 103 | // Manually edit underlying S.CL option to add validator logic. 104 | fun o -> 105 | o.Description <- "Path to C++ header file" 106 | o.IsRequired <- true 107 | o.AddValidator(fun result -> 108 | let file = result.GetValueForOption o 109 | if not file.Exists then 110 | result.ErrorMessage <- $"Header file not found: {file.FullName}" 111 | ) 112 | ) 113 | let library = Input.OptionRequired(["-l"; "--library"], description = "Name of native library to bind to") 114 | let output = Input.Option(["-o"; "--output"], description = "Output directory for generated code", defaultValue = "./output") 115 | let ns = Input.Option(["-n"; "--namespace"], description = "Namespace for generated code", defaultValue = "NativeBindings") 116 | let includes = Input.Option(["-i"; "--include-paths"], description = "Additional include paths") 117 | let defines = Input.Option(["-d"; "--defines"], description = "Preprocessor definitions (e.g., STM32L552xx)") 118 | let verbose = Input.Option(["-v"; "--verbose"], description = "Verbose output", defaultValue = false) 119 | 120 | let handler (header, library, output, ns, includes, defines, verbose) = 121 | let options = { 122 | HeaderFile = header 123 | LibraryName = library 124 | OutputDirectory = output 125 | Namespace = ns 126 | IncludePaths = includes |> Array.toList 127 | Defines = defines |> Array.toList 128 | Verbose = verbose 129 | } 130 | showHeader() 131 | showConfiguration options 132 | 133 | match runGeneration options with 134 | | Error errorMsg -> 135 | showError errorMsg 136 | 1 // Exit with error code 137 | | Ok _ -> 138 | showNextSteps options 139 | 0 // Success exit code 140 | 141 | command "generate" { 142 | description "Generate F# bindings for a native library" 143 | inputs (header, library, output, ns, includes, defines, verbose) 144 | setHandler handler 145 | } 146 | 147 | [] 148 | let main argv = 149 | rootCommand argv { 150 | description "Farscape: F# Native Library Binding Generator" 151 | setHandler id 152 | addCommand generateCommand 153 | } 154 | -------------------------------------------------------------------------------- /.serena/memories/native_binding_generation.md: -------------------------------------------------------------------------------- 1 | # Farscape's Role in Native Library Binding 2 | 3 | > **Architecture Update (December 2024)**: Farscape now generates **quotation-based output** with active patterns, 4 | > not just raw P/Invoke declarations. See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md`. 5 | 6 | ## Position in the Architecture 7 | 8 | Farscape is the **binding generator** - it transforms C/C++ headers into F# source code with quotations: 9 | 10 | ``` 11 | C Headers → Farscape → F# Source (Fidelity.[Target] + BAREWire.[Target] + Externs) 12 | ``` 13 | 14 | Farscape runs at **generation time**, before Firefly compilation. 15 | 16 | ## Farscape's Quotation-Based Outputs 17 | 18 | ### 1. Expr Quotations 19 | Memory layout information as quotations for nanopass consumption: 20 | 21 | ```fsharp 22 | let gpioQuotation: Expr = <@ 23 | { Name = "GPIO" 24 | Instances = Map.ofList [("GPIOA", 0x48000000un)] 25 | Layout = { Size = 0x400; Alignment = 4; Fields = gpioFields } 26 | MemoryRegion = Peripheral } 27 | @> 28 | ``` 29 | 30 | ### 2. Active Patterns for Recognition 31 | PSG pattern matching for Alex consumption: 32 | 33 | ```fsharp 34 | let (|GpioWritePin|_|) (node: PSGNode) : (string * int * uint32) option = ... 35 | let (|PeripheralAccess|_|) (node: PSGNode) : PeripheralAccessInfo option = ... 36 | ``` 37 | 38 | ### 3. MemoryModel Record 39 | Integration surface using pure F# record (not interface): 40 | 41 | ```fsharp 42 | let stm32l5MemoryModel: MemoryModel = { 43 | TargetFamily = "STM32L5" 44 | PeripheralDescriptors = [gpioQuotation; usartQuotation] 45 | RegisterConstraints = [accessConstraints] 46 | Regions = regionQuotation 47 | Recognize = recognizeMemoryOperation 48 | CacheTopology = None 49 | CoherencyModel = None 50 | } 51 | ``` 52 | 53 | ### 4. Fidelity.[Target] - High-Level F# API 54 | Developer-facing library with idiomatic F# types: 55 | 56 | ```fsharp 57 | // Fidelity.STM32L5/GPIO.fs 58 | module Fidelity.STM32L5.GPIO 59 | 60 | type Port = GPIOA | GPIOB | GPIOC | ... 61 | type Mode = Input | Output | Alternate | Analog 62 | 63 | let init (port: Port) (pin: int) (mode: Mode) : Result = ... 64 | let inline writePin (port: Port) (pin: int) (state: bool) : unit = ... 65 | ``` 66 | 67 | ### 2. BAREWire.[Target] - Memory Descriptors 68 | Compile-time hardware memory map using BAREWire.Core types: 69 | 70 | ```fsharp 71 | // BAREWire.STM32L5/Descriptors.fs 72 | let GPIO : PeripheralDescriptor = { 73 | Name = "GPIO" 74 | Region = MemoryRegionKind.Peripheral 75 | Instances = Map.ofList [("GPIOA", 0x48000000un); ...] 76 | Registers = [ 77 | { Name = "MODER"; Offset = 0x00; Width = 32; Access = ReadWrite; ... } 78 | { Name = "IDR"; Offset = 0x10; Width = 32; Access = ReadOnly; ... } 79 | { Name = "BSRR"; Offset = 0x18; Width = 32; Access = WriteOnly; ... } 80 | ] 81 | } 82 | ``` 83 | 84 | ### 3. Platform Bindings - Library Function Bindings 85 | Platform.Bindings modules for pre-compiled C library functions (BCL-free pattern): 86 | 87 | ```fsharp 88 | // Fidelity.STM32L5/HAL.fs - Platform.Bindings pattern 89 | // Alex provides MLIR emission that links against HAL library 90 | module Platform.Bindings.HAL.GPIO = 91 | let init (gpioX: nativeint) (gpioInit: nativeint) : HAL_StatusTypeDef = 92 | Unchecked.defaultof 93 | 94 | module Platform.Bindings.HAL.UART = 95 | let transmit (huart: nativeint) (pData: nativeint) (size: uint16) (timeout: uint32) : HAL_StatusTypeDef = 96 | Unchecked.defaultof 97 | ``` 98 | 99 | ## What Farscape Parses 100 | 101 | From C headers, Farscape extracts: 102 | 103 | | C Construct | F# Output | 104 | |-------------|-----------| 105 | | `typedef struct {...} XXX_TypeDef` | PeripheralDescriptor + F# record | 106 | | `__IO uint32_t FIELD` | Register with Access = ReadWrite | 107 | | `__I uint32_t FIELD` | Register with Access = ReadOnly | 108 | | `__O uint32_t FIELD` | Register with Access = WriteOnly | 109 | | `#define XXX_BASE (addr)` | Instance address in Instances map | 110 | | `#define XXX_Pos (n)` | BitField position | 111 | | `#define XXX_Msk (m)` | BitField width (computed from mask) | 112 | | `typedef enum {...}` | F# discriminated union | 113 | | `RetType FuncName(params)` | Platform.Bindings module function | 114 | 115 | ## Key CMSIS Patterns 116 | 117 | ```c 118 | // Access qualifiers → AccessKind 119 | #define __IO volatile // ReadWrite 120 | #define __I volatile const // ReadOnly 121 | #define __O volatile // WriteOnly 122 | 123 | // Bit field macros → BitFieldDescriptor 124 | #define USART_CR1_UE_Pos (0U) 125 | #define USART_CR1_UE_Msk (0x1UL << USART_CR1_UE_Pos) 126 | 127 | // Peripheral instance → address in Instances map 128 | #define GPIOA_BASE (0x48000000UL) 129 | #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) 130 | ``` 131 | 132 | ## Link-Time Consideration 133 | 134 | The extern declarations reference a **library name** (e.g., `"stm32l5xx_hal"`). 135 | 136 | At link time, the linker must find `libstm32l5xx_hal.a` containing: 137 | - `HAL_GPIO_Init` 138 | - `HAL_UART_Transmit` 139 | - etc. 140 | 141 | Farscape doesn't produce this library - it's pre-compiled by the vendor (STMicroelectronics). Farscape only produces the F# bindings that reference it. 142 | 143 | ## What Farscape Does NOT Do 144 | 145 | - Generate MLIR or LLVM code 146 | - Know about Alex's internal patterns 147 | - Make platform-specific code generation decisions 148 | - Compile the HAL library itself 149 | 150 | ## Relationship to Other Projects 151 | 152 | | Project | Relationship | 153 | |---------|--------------| 154 | | **BAREWire** | Farscape uses BAREWire.Core types; generates BAREWire.[Target] | 155 | | **Firefly** | Firefly compiles Farscape's output; Alex handles externs | 156 | | **Alloy** | Generated Fidelity.[Target] may use Alloy types | 157 | 158 | ## Canonical Document 159 | 160 | See Firefly `/docs/Native_Library_Binding_Architecture.md` for the complete binding architecture. 161 | -------------------------------------------------------------------------------- /.serena/memories/xparsec_header_parsing.md: -------------------------------------------------------------------------------- 1 | # XParsec-Based Header Parsing 2 | 3 | ## Architecture 4 | 5 | Farscape uses XParsec (the same parser combinator library that powers Firefly's PSG traversal) for parsing C/C++ headers. This provides: 6 | 7 | - Type-safe parsing with compile-time guarantees 8 | - Composable parsers for complex constructs 9 | - Excellent error messages with precise locations 10 | - Pure F# implementation 11 | 12 | ## Parser Structure 13 | 14 | ### Lexical Layer 15 | 16 | ```fsharp 17 | // Basic tokens 18 | let identifier = regex @"[a-zA-Z_][a-zA-Z0-9_]*" 19 | let number = regex @"[0-9]+" |>> int 20 | let hexNumber = regex @"0x[0-9a-fA-F]+" |>> parseHex 21 | 22 | // Keywords 23 | let kwStruct = keyword "struct" 24 | let kwTypedef = keyword "typedef" 25 | let kwVolatile = keyword "volatile" 26 | let kwConst = keyword "const" 27 | 28 | // CMSIS qualifiers 29 | let cmsis_I = keyword "__I" >>% CMSIS_I 30 | let cmsis_O = keyword "__O" >>% CMSIS_O 31 | let cmsis_IO = keyword "__IO" >>% CMSIS_IO 32 | ``` 33 | 34 | ### Type Parsing 35 | 36 | ```fsharp 37 | // C type specifiers 38 | let typeSpecifier = 39 | choice [ 40 | keyword "uint32_t" >>% CType.UInt32 41 | keyword "uint16_t" >>% CType.UInt16 42 | keyword "uint8_t" >>% CType.UInt8 43 | keyword "int32_t" >>% CType.Int32 44 | keyword "void" >>% CType.Void 45 | identifier |>> CType.Named 46 | ] 47 | 48 | // Pointer types with qualifiers 49 | let pointerType = 50 | pipe3 (many qualifier) typeSpecifier (many1 (symbol "*")) 51 | (fun quals baseType ptrs -> CType.Pointer(baseType, quals, List.length ptrs)) 52 | ``` 53 | 54 | ### Structure Parsing 55 | 56 | ```fsharp 57 | // Field with CMSIS qualifiers 58 | let fieldDecl = 59 | parse { 60 | let! quals = many (cmsis_I <|> cmsis_O <|> cmsis_IO <|> kwVolatile <|> kwConst) 61 | let! fieldType = typeSpecifier 62 | let! name = identifier 63 | let! arraySize = optional (between (symbol "[") (symbol "]") number) 64 | do! symbol ";" 65 | return { Qualifiers = quals; Type = fieldType; Name = name; ArraySize = arraySize } 66 | } 67 | 68 | // Struct definition 69 | let structDef = 70 | parse { 71 | do! kwStruct 72 | let! name = optional identifier 73 | do! symbol "{" 74 | let! fields = many fieldDecl 75 | do! symbol "}" 76 | return { Name = name; Fields = fields } 77 | } 78 | 79 | // Typedef struct 80 | let typedefStruct = 81 | parse { 82 | do! kwTypedef 83 | let! struct' = structDef 84 | let! aliasName = identifier 85 | do! symbol ";" 86 | return { Struct = struct'; Alias = aliasName } 87 | } 88 | ``` 89 | 90 | ### Macro Parsing 91 | 92 | ```fsharp 93 | // Base address macros: #define GPIOA_BASE 0x48000000UL 94 | let baseAddressMacro = 95 | parse { 96 | do! symbol "#define" 97 | let! name = identifier 98 | let! addr = hexNumber 99 | do! optional (keyword "UL" <|> keyword "U") 100 | return MacroDecl.BaseAddress(name, addr) 101 | } 102 | 103 | // Bit position macros: #define USART_CR1_UE_Pos 0U 104 | let bitPosMacro = 105 | parse { 106 | do! symbol "#define" 107 | let! name = regex @"[A-Z0-9_]+_Pos" 108 | let! pos = number 109 | do! optional (keyword "U") 110 | return MacroDecl.BitPosition(name, pos) 111 | } 112 | 113 | // Bit mask macros: #define USART_CR1_UE_Msk (0x1UL << USART_CR1_UE_Pos) 114 | let bitMaskMacro = 115 | parse { 116 | do! symbol "#define" 117 | let! name = regex @"[A-Z0-9_]+_Msk" 118 | do! symbol "(" 119 | let! value = hexNumber 120 | do! keyword "UL" <|> keyword "U" 121 | do! symbol "<<" 122 | let! posRef = identifier 123 | do! symbol ")" 124 | return MacroDecl.BitMask(name, value, posRef) 125 | } 126 | ``` 127 | 128 | ### Function Declaration Parsing 129 | 130 | ```fsharp 131 | // Function parameter 132 | let parameter = 133 | parse { 134 | let! paramType = pointerType <|> (typeSpecifier |>> fun t -> CType.Value t) 135 | let! name = identifier 136 | return { Type = paramType; Name = name } 137 | } 138 | 139 | // Function declaration 140 | let functionDecl = 141 | parse { 142 | let! retType = typeSpecifier 143 | let! name = identifier 144 | do! symbol "(" 145 | let! params = sepBy parameter (symbol ",") 146 | do! symbol ")" 147 | do! symbol ";" 148 | return { ReturnType = retType; Name = name; Parameters = params } 149 | } 150 | ``` 151 | 152 | ## Output Types 153 | 154 | Parsing produces intermediate types that map to quotation generation: 155 | 156 | ```fsharp 157 | type ParsedStruct = { 158 | Name: string option 159 | Alias: string option 160 | Fields: ParsedField list 161 | } 162 | 163 | type ParsedField = { 164 | Qualifiers: Qualifier list // Including CMSIS __I, __O, __IO 165 | Type: CType 166 | Name: string 167 | ArraySize: int option 168 | } 169 | 170 | type ParsedMacro = 171 | | BaseAddress of name: string * address: uint64 172 | | BitPosition of name: string * position: int 173 | | BitMask of name: string * value: uint64 * positionRef: string 174 | | PeripheralInstance of name: string * typeName: string * baseRef: string 175 | 176 | type ParsedFunction = { 177 | ReturnType: CType 178 | Name: string 179 | Parameters: ParsedParameter list 180 | } 181 | ``` 182 | 183 | ## CMSIS Qualifier Extraction 184 | 185 | The key transformation - CMSIS qualifiers become `AccessKind`: 186 | 187 | ```fsharp 188 | let extractAccessKind (quals: Qualifier list) : AccessKind = 189 | if List.contains CMSIS_I quals then ReadOnly 190 | elif List.contains CMSIS_O quals then WriteOnly 191 | elif List.contains CMSIS_IO quals then ReadWrite 192 | elif List.contains Const quals && List.contains Volatile quals then ReadOnly 193 | elif List.contains Volatile quals then ReadWrite 194 | else ReadWrite // Default for non-volatile 195 | ``` 196 | 197 | ## Canonical Reference 198 | 199 | See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md` for how parsed output feeds into quotation generation. 200 | -------------------------------------------------------------------------------- /.serena/.serena/memories/xparsec_header_parsing.md: -------------------------------------------------------------------------------- 1 | # XParsec-Based Header Parsing 2 | 3 | ## Architecture 4 | 5 | Farscape uses XParsec (the same parser combinator library that powers Firefly's PSG traversal) for parsing C/C++ headers. This provides: 6 | 7 | - Type-safe parsing with compile-time guarantees 8 | - Composable parsers for complex constructs 9 | - Excellent error messages with precise locations 10 | - Pure F# implementation 11 | 12 | ## Parser Structure 13 | 14 | ### Lexical Layer 15 | 16 | ```fsharp 17 | // Basic tokens 18 | let identifier = regex @"[a-zA-Z_][a-zA-Z0-9_]*" 19 | let number = regex @"[0-9]+" |>> int 20 | let hexNumber = regex @"0x[0-9a-fA-F]+" |>> parseHex 21 | 22 | // Keywords 23 | let kwStruct = keyword "struct" 24 | let kwTypedef = keyword "typedef" 25 | let kwVolatile = keyword "volatile" 26 | let kwConst = keyword "const" 27 | 28 | // CMSIS qualifiers 29 | let cmsis_I = keyword "__I" >>% CMSIS_I 30 | let cmsis_O = keyword "__O" >>% CMSIS_O 31 | let cmsis_IO = keyword "__IO" >>% CMSIS_IO 32 | ``` 33 | 34 | ### Type Parsing 35 | 36 | ```fsharp 37 | // C type specifiers 38 | let typeSpecifier = 39 | choice [ 40 | keyword "uint32_t" >>% CType.UInt32 41 | keyword "uint16_t" >>% CType.UInt16 42 | keyword "uint8_t" >>% CType.UInt8 43 | keyword "int32_t" >>% CType.Int32 44 | keyword "void" >>% CType.Void 45 | identifier |>> CType.Named 46 | ] 47 | 48 | // Pointer types with qualifiers 49 | let pointerType = 50 | pipe3 (many qualifier) typeSpecifier (many1 (symbol "*")) 51 | (fun quals baseType ptrs -> CType.Pointer(baseType, quals, List.length ptrs)) 52 | ``` 53 | 54 | ### Structure Parsing 55 | 56 | ```fsharp 57 | // Field with CMSIS qualifiers 58 | let fieldDecl = 59 | parse { 60 | let! quals = many (cmsis_I <|> cmsis_O <|> cmsis_IO <|> kwVolatile <|> kwConst) 61 | let! fieldType = typeSpecifier 62 | let! name = identifier 63 | let! arraySize = optional (between (symbol "[") (symbol "]") number) 64 | do! symbol ";" 65 | return { Qualifiers = quals; Type = fieldType; Name = name; ArraySize = arraySize } 66 | } 67 | 68 | // Struct definition 69 | let structDef = 70 | parse { 71 | do! kwStruct 72 | let! name = optional identifier 73 | do! symbol "{" 74 | let! fields = many fieldDecl 75 | do! symbol "}" 76 | return { Name = name; Fields = fields } 77 | } 78 | 79 | // Typedef struct 80 | let typedefStruct = 81 | parse { 82 | do! kwTypedef 83 | let! struct' = structDef 84 | let! aliasName = identifier 85 | do! symbol ";" 86 | return { Struct = struct'; Alias = aliasName } 87 | } 88 | ``` 89 | 90 | ### Macro Parsing 91 | 92 | ```fsharp 93 | // Base address macros: #define GPIOA_BASE 0x48000000UL 94 | let baseAddressMacro = 95 | parse { 96 | do! symbol "#define" 97 | let! name = identifier 98 | let! addr = hexNumber 99 | do! optional (keyword "UL" <|> keyword "U") 100 | return MacroDecl.BaseAddress(name, addr) 101 | } 102 | 103 | // Bit position macros: #define USART_CR1_UE_Pos 0U 104 | let bitPosMacro = 105 | parse { 106 | do! symbol "#define" 107 | let! name = regex @"[A-Z0-9_]+_Pos" 108 | let! pos = number 109 | do! optional (keyword "U") 110 | return MacroDecl.BitPosition(name, pos) 111 | } 112 | 113 | // Bit mask macros: #define USART_CR1_UE_Msk (0x1UL << USART_CR1_UE_Pos) 114 | let bitMaskMacro = 115 | parse { 116 | do! symbol "#define" 117 | let! name = regex @"[A-Z0-9_]+_Msk" 118 | do! symbol "(" 119 | let! value = hexNumber 120 | do! keyword "UL" <|> keyword "U" 121 | do! symbol "<<" 122 | let! posRef = identifier 123 | do! symbol ")" 124 | return MacroDecl.BitMask(name, value, posRef) 125 | } 126 | ``` 127 | 128 | ### Function Declaration Parsing 129 | 130 | ```fsharp 131 | // Function parameter 132 | let parameter = 133 | parse { 134 | let! paramType = pointerType <|> (typeSpecifier |>> fun t -> CType.Value t) 135 | let! name = identifier 136 | return { Type = paramType; Name = name } 137 | } 138 | 139 | // Function declaration 140 | let functionDecl = 141 | parse { 142 | let! retType = typeSpecifier 143 | let! name = identifier 144 | do! symbol "(" 145 | let! params = sepBy parameter (symbol ",") 146 | do! symbol ")" 147 | do! symbol ";" 148 | return { ReturnType = retType; Name = name; Parameters = params } 149 | } 150 | ``` 151 | 152 | ## Output Types 153 | 154 | Parsing produces intermediate types that map to quotation generation: 155 | 156 | ```fsharp 157 | type ParsedStruct = { 158 | Name: string option 159 | Alias: string option 160 | Fields: ParsedField list 161 | } 162 | 163 | type ParsedField = { 164 | Qualifiers: Qualifier list // Including CMSIS __I, __O, __IO 165 | Type: CType 166 | Name: string 167 | ArraySize: int option 168 | } 169 | 170 | type ParsedMacro = 171 | | BaseAddress of name: string * address: uint64 172 | | BitPosition of name: string * position: int 173 | | BitMask of name: string * value: uint64 * positionRef: string 174 | | PeripheralInstance of name: string * typeName: string * baseRef: string 175 | 176 | type ParsedFunction = { 177 | ReturnType: CType 178 | Name: string 179 | Parameters: ParsedParameter list 180 | } 181 | ``` 182 | 183 | ## CMSIS Qualifier Extraction 184 | 185 | The key transformation - CMSIS qualifiers become `AccessKind`: 186 | 187 | ```fsharp 188 | let extractAccessKind (quals: Qualifier list) : AccessKind = 189 | if List.contains CMSIS_I quals then ReadOnly 190 | elif List.contains CMSIS_O quals then WriteOnly 191 | elif List.contains CMSIS_IO quals then ReadWrite 192 | elif List.contains Const quals && List.contains Volatile quals then ReadOnly 193 | elif List.contains Volatile quals then ReadWrite 194 | else ReadWrite // Default for non-volatile 195 | ``` 196 | 197 | ## Canonical Reference 198 | 199 | See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md` for how parsed output feeds into quotation generation. 200 | -------------------------------------------------------------------------------- /src/Farscape.Core/TypeMapper.fs: -------------------------------------------------------------------------------- 1 | namespace Farscape.Core 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.Runtime.InteropServices 6 | open System.Text.RegularExpressions 7 | 8 | module TypeMapper = 9 | type TypeMapping = { 10 | OriginalName: string 11 | FSharpName: string 12 | MarshalAs: MarshalAsAttribute option 13 | IsPointer: bool 14 | IsConst: bool 15 | IsPrimitive: bool 16 | IsArray: bool 17 | ArrayLength: int option 18 | } 19 | 20 | let private typeMap = 21 | dict [ 22 | // Primitive types 23 | "void", "unit" 24 | "bool", "bool" 25 | "char", "byte" 26 | "signed char", "sbyte" 27 | "unsigned char", "byte" 28 | "short", "int16" 29 | "unsigned short", "uint16" 30 | "int", "int32" 31 | "unsigned int", "uint32" 32 | "long", "int32" // This can be platform dependent 33 | "unsigned long", "uint32" 34 | "long long", "int64" 35 | "unsigned long long", "uint64" 36 | "float", "single" 37 | "double", "double" 38 | "int8_t", "sbyte" 39 | "uint8_t", "byte" 40 | "int16_t", "int16" 41 | "uint16_t", "uint16" 42 | "int32_t", "int32" 43 | "uint32_t", "uint32" 44 | "int64_t", "int64" 45 | "uint64_t", "uint64" 46 | "size_t", "nativeint" 47 | "ptrdiff_t", "nativeint" 48 | "intptr_t", "nativeint" 49 | "uintptr_t", "unativeint" 50 | "wchar_t", "char" 51 | "char16_t", "char" 52 | "char32_t", "uint32" 53 | // Pointer types 54 | "void*", "nativeint" 55 | "char*", "string" 56 | "const char*", "string" 57 | "wchar_t*", "string" 58 | "const wchar_t*", "string" 59 | ] 60 | 61 | let isPrimitiveType (typeName: string) = 62 | typeMap.ContainsKey(typeName) || 63 | typeName.EndsWith("*") && typeMap.ContainsKey(typeName) 64 | 65 | let isPointerType (typeName: string) = 66 | typeName.Contains("*") || typeName.EndsWith("&") 67 | 68 | let isConstType (typeName: string) = 69 | typeName.StartsWith("const ") || typeName.Contains(" const") 70 | 71 | let isArrayType (typeName: string) = 72 | typeName.Contains("[") && typeName.Contains("]") 73 | 74 | let getArrayLength (typeName: string) = 75 | let match' = Regex.Match(typeName, @"\[(\d+)\]") 76 | if match'.Success then 77 | Some(Int32.Parse(match'.Groups.[1].Value)) 78 | else 79 | None 80 | 81 | let cleanTypeName (typeName: string) = 82 | typeName 83 | .Replace("const ", "") 84 | .Replace(" const", "") 85 | .Replace("&", "") 86 | .Replace("*", "") 87 | .Replace("struct ", "") 88 | .Replace("class ", "") 89 | .Replace("enum ", "") 90 | .Replace("union ", "") 91 | .Trim() 92 | 93 | let getFSharpType (cppType: string) : string = 94 | let cleaned = cleanTypeName cppType 95 | 96 | if typeMap.ContainsKey(cleaned) then 97 | typeMap.[cleaned] 98 | elif typeMap.ContainsKey(cppType) then 99 | typeMap.[cppType] 100 | elif isPointerType cppType then 101 | if cppType.Contains("char*") || cppType.Contains("const char*") then 102 | "string" 103 | else 104 | "nativeint" 105 | else 106 | cleaned 107 | 108 | let getMarshalAs (cppType: string) : MarshalAsAttribute option = 109 | let marshalType = 110 | if cppType.Contains("char*") || cppType.Contains("const char*") then 111 | Some(UnmanagedType.LPStr) 112 | elif cppType.Contains("wchar_t*") || cppType.Contains("const wchar_t*") then 113 | Some(UnmanagedType.LPWStr) 114 | elif isArrayType cppType then 115 | Some(UnmanagedType.LPArray) 116 | else 117 | None 118 | 119 | marshalType |> Option.map (fun t -> MarshalAsAttribute(t)) 120 | 121 | let mapType (cppType: string) : TypeMapping = 122 | { 123 | OriginalName = cppType 124 | FSharpName = getFSharpType cppType 125 | MarshalAs = getMarshalAs cppType 126 | IsPointer = isPointerType cppType 127 | IsConst = isConstType cppType 128 | IsPrimitive = isPrimitiveType cppType 129 | IsArray = isArrayType cppType 130 | ArrayLength = getArrayLength cppType 131 | } 132 | 133 | let mapTypes (declarations: CppParser.Declaration list) : TypeMapping list = 134 | let rec collectTypes (decls: CppParser.Declaration list) = 135 | let mutable types = [] 136 | 137 | for decl in decls do 138 | match decl with 139 | | CppParser.Declaration.Function f -> 140 | types <- mapType f.ReturnType :: types 141 | for (_, paramType) in f.Parameters do 142 | types <- mapType paramType :: types 143 | 144 | | CppParser.Declaration.Struct s -> 145 | types <- mapType s.Name :: types 146 | for field in s.Fields do 147 | types <- mapType field.Type :: types 148 | 149 | | CppParser.Declaration.Macro _ -> 150 | () // Macros don't contribute to type mappings 151 | 152 | | CppParser.Declaration.Enum e -> 153 | types <- mapType e.Name :: types 154 | 155 | | CppParser.Declaration.Typedef t -> 156 | types <- mapType t.Name :: types 157 | types <- mapType t.UnderlyingType :: types 158 | 159 | | CppParser.Declaration.Namespace ns -> 160 | types <- collectTypes ns.Declarations @ types 161 | 162 | | CppParser.Declaration.Class c -> 163 | types <- mapType c.Name :: types 164 | for field in c.Fields do 165 | types <- mapType field.Type :: types 166 | types <- collectTypes c.Methods @ types 167 | 168 | types 169 | 170 | collectTypes declarations 171 | |> List.distinctBy (fun t -> t.OriginalName) -------------------------------------------------------------------------------- /src/Farscape.Core/DelegatePointer.fs: -------------------------------------------------------------------------------- 1 | namespace Farscape.Core 2 | 3 | open System 4 | open System.Text 5 | open System.Runtime.InteropServices 6 | 7 | module DelegatePointer = 8 | type FunctionPointerSignature = { 9 | ReturnType: string 10 | ParameterTypes: string list 11 | CallingConvention: CallingConvention 12 | } 13 | 14 | type DelegateTypeDefinition = { 15 | Name: string 16 | Signature: FunctionPointerSignature 17 | Documentation: string option 18 | } 19 | 20 | let extractFunctionPointerSignature (cppType: string) : FunctionPointerSignature option = 21 | let pattern = @"([\w\s\*]+)\s*\(\*\)\s*\(([\w\s\*,]*)\)" 22 | let regex = System.Text.RegularExpressions.Regex(pattern) 23 | let match' = regex.Match(cppType) 24 | 25 | if match'.Success then 26 | let returnType = match'.Groups.[1].Value.Trim() 27 | let paramStr = match'.Groups.[2].Value.Trim() 28 | 29 | let paramTypes = 30 | if String.IsNullOrWhiteSpace(paramStr) then 31 | [] 32 | else 33 | paramStr.Split(',') 34 | |> Array.map (fun s -> s.Trim()) 35 | |> Array.toList 36 | 37 | Some { 38 | ReturnType = returnType 39 | ParameterTypes = paramTypes 40 | CallingConvention = CallingConvention.Cdecl 41 | } 42 | else 43 | None 44 | 45 | let generateDelegateType (signature: FunctionPointerSignature) (name: string) (documentation: string option) = 46 | let parameters = 47 | signature.ParameterTypes 48 | |> List.mapi (fun i paramType -> 49 | let fsharpType = TypeMapper.getFSharpType paramType 50 | $"arg{i}: {fsharpType}") 51 | |> String.concat ", " 52 | 53 | let returnType = TypeMapper.getFSharpType signature.ReturnType 54 | 55 | let docComment = 56 | match documentation with 57 | | None -> "" 58 | | Some text -> 59 | let lines = text.Split([|'\n'|], StringSplitOptions.RemoveEmptyEntries) 60 | let sb = StringBuilder() 61 | sb.AppendLine("/// ") |> ignore 62 | for line in lines do 63 | sb.AppendLine($"/// {line.Trim()}") |> ignore 64 | sb.AppendLine("/// ") |> ignore 65 | sb.ToString() 66 | 67 | let callingConvention = signature.CallingConvention.ToString() 68 | 69 | $"{docComment}[]\ntype {name} = delegate of {parameters} -> {returnType}" 70 | 71 | let generateDelegateWrapper (delegateType: string) = 72 | $"let wrap{delegateType} (func: {delegateType}) : nativeint =\n Marshal.GetFunctionPointerForDelegate(func) |> nativeint" 73 | 74 | let generateDelegateUnwrapper (delegateType: string) = 75 | $"let unwrap{delegateType} (ptr: nativeint) : {delegateType} =\n Marshal.GetDelegateForFunctionPointer(ptr, typeof<{delegateType}>) :?> {delegateType}" 76 | 77 | let identifyFunctionPointers (declarations: CppParser.Declaration list) : DelegateTypeDefinition list = 78 | let rec processDeclarations (decls: CppParser.Declaration list) = 79 | let mutable delegates = [] 80 | 81 | // Helper to process parameters 82 | let processParameters (parameters: (string * string) list) = 83 | for (name, paramType) in parameters do 84 | match extractFunctionPointerSignature paramType with 85 | | Some signature -> 86 | let delegateName = $"{name}Delegate" 87 | delegates <- { Name = delegateName; Signature = signature; Documentation = None } :: delegates 88 | | None -> () 89 | 90 | for decl in decls do 91 | match decl with 92 | | CppParser.Declaration.Function f -> 93 | processParameters f.Parameters 94 | 95 | // Check if return type is a function pointer 96 | match extractFunctionPointerSignature f.ReturnType with 97 | | Some signature -> 98 | let delegateName = $"{f.Name}ReturnDelegate" 99 | delegates <- { Name = delegateName; Signature = signature; Documentation = None } :: delegates 100 | | None -> () 101 | 102 | | CppParser.Declaration.Struct s -> 103 | for field in s.Fields do 104 | match extractFunctionPointerSignature field.Type with 105 | | Some signature -> 106 | let delegateName = $"{s.Name}_{field.Name}Delegate" 107 | delegates <- { Name = delegateName; Signature = signature; Documentation = None } :: delegates 108 | | None -> () 109 | 110 | | CppParser.Declaration.Class c -> 111 | processDeclarations c.Methods |> ignore 112 | 113 | for field in c.Fields do 114 | match extractFunctionPointerSignature field.Type with 115 | | Some signature -> 116 | let delegateName = $"{c.Name}_{field.Name}Delegate" 117 | delegates <- { Name = delegateName; Signature = signature; Documentation = None } :: delegates 118 | | None -> () 119 | 120 | | CppParser.Declaration.Namespace ns -> 121 | delegates <- processDeclarations ns.Declarations @ delegates 122 | 123 | | _ -> () 124 | 125 | List.distinctBy (fun d -> d.Name) delegates 126 | 127 | processDeclarations declarations 128 | 129 | let generateDelegateTypes (functionPointers: DelegateTypeDefinition list) = 130 | functionPointers 131 | |> List.map (fun fp -> 132 | generateDelegateType fp.Signature fp.Name fp.Documentation) 133 | |> String.concat "\n\n" 134 | 135 | let generateDelegateWrappers (functionPointers: DelegateTypeDefinition list) = 136 | functionPointers 137 | |> List.map (fun fp -> generateDelegateWrapper fp.Name) 138 | |> String.concat "\n\n" 139 | 140 | let generateDelegateUnwrappers (functionPointers: DelegateTypeDefinition list) = 141 | functionPointers 142 | |> List.map (fun fp -> generateDelegateUnwrapper fp.Name) 143 | |> String.concat "\n\n" -------------------------------------------------------------------------------- /.serena/memories/farscape_barewire_integration.md: -------------------------------------------------------------------------------- 1 | # Farscape + BAREWire Integration Architecture 2 | 3 | > **Architecture Update (December 2024)**: Farscape now generates **quotation-based output** with active patterns. 4 | > See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md` for the unified architecture. 5 | 6 | ## Quotation-Based Output 7 | 8 | Farscape generates three artifact types using F# quotations and active patterns: 9 | 10 | 1. **Expr** - Quotations encoding hardware memory layout 11 | 2. **Active Patterns** - PSG recognition patterns like `(|GpioWritePin|_|)` 12 | 3. **MemoryModel Record** - Integration surface for fsnative nanopass pipeline 13 | 14 | ```fsharp 15 | // Generated quotation for GPIO peripheral 16 | let gpioPeripheralQuotation: Expr = <@ 17 | { Name = "GPIO" 18 | Instances = Map.ofList [("GPIOA", 0x48000000un); ("GPIOB", 0x48000400un)] 19 | Layout = { Size = 0x400; Alignment = 4; Fields = gpioFields } 20 | MemoryRegion = Peripheral } 21 | @> 22 | 23 | // Active pattern for PSG recognition 24 | let (|GpioWritePin|_|) (node: PSGNode) : (string * int * uint32) option = ... 25 | 26 | // MemoryModel for fsnative integration 27 | let stm32l5MemoryModel: MemoryModel = { 28 | TargetFamily = "STM32L5" 29 | PeripheralDescriptors = [gpioPeripheralQuotation; usartPeripheralQuotation] 30 | RegisterConstraints = [gpioConstraints] 31 | Regions = regionQuotation 32 | Recognize = recognizeMemoryOperation 33 | CacheTopology = None 34 | CoherencyModel = None 35 | } 36 | ``` 37 | 38 | ## Core Design Principle: Invisible Memory Management 39 | 40 | Farscape is NOT just a C/C++ binding generator. For hardware targets like CMSIS/STM32, it builds a **complete model of the hardware memory system** that enables the Fidelity compiler to manage memory on behalf of the developer. 41 | 42 | From "Memory Management By Choice": 43 | > "BAREWire takes a fundamentally different approach. Rather than demanding constant attention to memory, it provides an opt-in model where developers can accept compiler-generated memory layouts for most code" 44 | 45 | The developer writes clean F#: 46 | ```fsharp 47 | let toggleLed () = 48 | HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5) 49 | ``` 50 | 51 | The compiler (via tree-shaking/reachability) determines: 52 | - Which peripherals are used 53 | - Their memory-mapped addresses 54 | - Register offsets and access constraints 55 | - Volatile semantics 56 | 57 | **The developer never sees BARELayout or offset calculations.** 58 | 59 | ## Farscape Output: Three Artifacts 60 | 61 | For CMSIS and similar hardware targets, Farscape generates: 62 | 63 | 1. **F# Types** - Struct definitions (`GPIO_TypeDef`, etc.) 64 | 2. **Extern Declarations** - Function bindings (`HAL_GPIO_Init`, etc.) 65 | 3. **Memory Descriptors** - BAREWire-compatible hardware memory catalog 66 | 67 | ## The Memory Descriptor Model 68 | 69 | Farscape must didactically catalog the entire hardware memory architecture: 70 | 71 | ```fsharp 72 | type PeripheralDescriptor = { 73 | Name: string // "GPIO" 74 | Instances: Map // GPIOA → 0x48000000, etc. 75 | Layout: PeripheralLayout 76 | MemoryRegion: MemoryRegionKind // Peripheral, SRAM, Flash, System 77 | } 78 | 79 | and PeripheralLayout = { 80 | Size: int 81 | Alignment: int 82 | Fields: FieldDescriptor list 83 | } 84 | 85 | and FieldDescriptor = { 86 | Name: string 87 | Offset: int 88 | Type: RegisterType 89 | Access: AccessKind // ReadOnly | WriteOnly | ReadWrite 90 | BitFields: BitFieldDescriptor list option // For _Pos/_Msk macros 91 | Documentation: string option 92 | } 93 | 94 | and AccessKind = 95 | | ReadOnly // __I - reads hardware state 96 | | WriteOnly // __O - writes trigger hardware action, reads undefined 97 | | ReadWrite // __IO - normal read/write 98 | 99 | and MemoryRegionKind = 100 | | Flash // Execute-in-place, read-only at runtime 101 | | SRAM // Normal read/write memory 102 | | Peripheral // Memory-mapped I/O, volatile, uncacheable 103 | | SystemControl // ARM system peripherals (NVIC, etc.) 104 | ``` 105 | 106 | ## CMSIS Qualifier Semantics 107 | 108 | The `__I`, `__O`, `__IO` qualifiers encode hardware constraints: 109 | 110 | | Qualifier | Meaning | Code Gen Implication | 111 | |-----------|---------|---------------------| 112 | | `__I` (volatile const) | Read-only register | Writes are UB, emit read-only access | 113 | | `__O` (volatile) | Write-only register | Reads return undefined, emit write-only | 114 | | `__IO` (volatile) | Read-write register | Normal volatile access | 115 | 116 | Writing to `IDR` (input register) or reading from `BSRR` (bit set/reset) is a hardware error. 117 | 118 | ## Microcontroller Memory Map Reality 119 | 120 | | Region | Address Range | Characteristics | 121 | |--------|---------------|-----------------| 122 | | Flash | `0x0800_0000` | Code + constants, read-only at runtime | 123 | | SRAM | `0x2000_0000` | Stack, heap, .bss, .data | 124 | | Peripherals | `0x4000_0000+` | Memory-mapped I/O, volatile, specific access widths | 125 | | System | `0xE000_0000` | NVIC, SysTick, debug - ARM core peripherals | 126 | 127 | ## Dependency: Farscape → BAREWire 128 | 129 | Farscape should take BAREWire as a dependency. The memory descriptor types live in BAREWire; Farscape populates them from parsed headers. 130 | 131 | This means BAREWire development may need to advance in parallel with Farscape to provide: 132 | - `PeripheralDescriptor` and related types 133 | - Memory region abstractions 134 | - Hardware address mapping primitives 135 | 136 | ## Pipeline Flow 137 | 138 | ``` 139 | CMSIS Header (.h) 140 | ↓ Farscape parses (clang JSON AST + macros) 141 | ↓ 142 | Farscape Output: 143 | ├── Types.fs (F# structs) 144 | ├── Bindings.fs (extern declarations) 145 | └── Descriptors (BAREWire memory catalog) 146 | ↓ 147 | PSG (contains type defs + extern markers + layout refs) 148 | ↓ 149 | Alex/Zipper traversal: 150 | ├── Peripheral access → MLIR volatile load/store 151 | ├── Layout info → correct offsets 152 | └── HAL functions → inline or linker symbol 153 | ↓ 154 | MLIR → LLVM → Native Binary 155 | ``` 156 | 157 | ## Tree-Shaking Drives Inclusion 158 | 159 | The massive CMSIS headers (20K+ lines, 15K+ macros) get tree-shaken to only what's used: 160 | 161 | 1. Reachability analysis identifies used peripherals/functions 162 | 2. Only referenced descriptors included in final artifact 163 | 3. Final binary is as tight as hand-written C 164 | 165 | ## Developer Experience 166 | 167 | 1. Add dependency in fidproj: `farscape-stm32l5 = { path = "..." }` 168 | 2. Write clean F# using typed peripheral access 169 | 3. Compile - Firefly handles all memory management 170 | 171 | The infrastructure is invisible unless the developer explicitly opts into manual control. 172 | -------------------------------------------------------------------------------- /.serena/.serena/memories/farscape_barewire_integration.md: -------------------------------------------------------------------------------- 1 | # Farscape + BAREWire Integration Architecture 2 | 3 | > **Architecture Update (December 2024)**: Farscape now generates **quotation-based output** with active patterns. 4 | > See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md` for the unified architecture. 5 | 6 | ## Quotation-Based Output 7 | 8 | Farscape generates three artifact types using F# quotations and active patterns: 9 | 10 | 1. **Expr** - Quotations encoding hardware memory layout 11 | 2. **Active Patterns** - PSG recognition patterns like `(|GpioWritePin|_|)` 12 | 3. **MemoryModel Record** - Integration surface for fsnative nanopass pipeline 13 | 14 | ```fsharp 15 | // Generated quotation for GPIO peripheral 16 | let gpioPeripheralQuotation: Expr = <@ 17 | { Name = "GPIO" 18 | Instances = Map.ofList [("GPIOA", 0x48000000un); ("GPIOB", 0x48000400un)] 19 | Layout = { Size = 0x400; Alignment = 4; Fields = gpioFields } 20 | MemoryRegion = Peripheral } 21 | @> 22 | 23 | // Active pattern for PSG recognition 24 | let (|GpioWritePin|_|) (node: PSGNode) : (string * int * uint32) option = ... 25 | 26 | // MemoryModel for fsnative integration 27 | let stm32l5MemoryModel: MemoryModel = { 28 | TargetFamily = "STM32L5" 29 | PeripheralDescriptors = [gpioPeripheralQuotation; usartPeripheralQuotation] 30 | RegisterConstraints = [gpioConstraints] 31 | Regions = regionQuotation 32 | Recognize = recognizeMemoryOperation 33 | CacheTopology = None 34 | CoherencyModel = None 35 | } 36 | ``` 37 | 38 | ## Core Design Principle: Invisible Memory Management 39 | 40 | Farscape is NOT just a C/C++ binding generator. For hardware targets like CMSIS/STM32, it builds a **complete model of the hardware memory system** that enables the Fidelity compiler to manage memory on behalf of the developer. 41 | 42 | From "Memory Management By Choice": 43 | > "BAREWire takes a fundamentally different approach. Rather than demanding constant attention to memory, it provides an opt-in model where developers can accept compiler-generated memory layouts for most code" 44 | 45 | The developer writes clean F#: 46 | ```fsharp 47 | let toggleLed () = 48 | HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5) 49 | ``` 50 | 51 | The compiler (via tree-shaking/reachability) determines: 52 | - Which peripherals are used 53 | - Their memory-mapped addresses 54 | - Register offsets and access constraints 55 | - Volatile semantics 56 | 57 | **The developer never sees BARELayout or offset calculations.** 58 | 59 | ## Farscape Output: Three Artifacts 60 | 61 | For CMSIS and similar hardware targets, Farscape generates: 62 | 63 | 1. **F# Types** - Struct definitions (`GPIO_TypeDef`, etc.) 64 | 2. **Extern Declarations** - Function bindings (`HAL_GPIO_Init`, etc.) 65 | 3. **Memory Descriptors** - BAREWire-compatible hardware memory catalog 66 | 67 | ## The Memory Descriptor Model 68 | 69 | Farscape must didactically catalog the entire hardware memory architecture: 70 | 71 | ```fsharp 72 | type PeripheralDescriptor = { 73 | Name: string // "GPIO" 74 | Instances: Map // GPIOA → 0x48000000, etc. 75 | Layout: PeripheralLayout 76 | MemoryRegion: MemoryRegionKind // Peripheral, SRAM, Flash, System 77 | } 78 | 79 | and PeripheralLayout = { 80 | Size: int 81 | Alignment: int 82 | Fields: FieldDescriptor list 83 | } 84 | 85 | and FieldDescriptor = { 86 | Name: string 87 | Offset: int 88 | Type: RegisterType 89 | Access: AccessKind // ReadOnly | WriteOnly | ReadWrite 90 | BitFields: BitFieldDescriptor list option // For _Pos/_Msk macros 91 | Documentation: string option 92 | } 93 | 94 | and AccessKind = 95 | | ReadOnly // __I - reads hardware state 96 | | WriteOnly // __O - writes trigger hardware action, reads undefined 97 | | ReadWrite // __IO - normal read/write 98 | 99 | and MemoryRegionKind = 100 | | Flash // Execute-in-place, read-only at runtime 101 | | SRAM // Normal read/write memory 102 | | Peripheral // Memory-mapped I/O, volatile, uncacheable 103 | | SystemControl // ARM system peripherals (NVIC, etc.) 104 | ``` 105 | 106 | ## CMSIS Qualifier Semantics 107 | 108 | The `__I`, `__O`, `__IO` qualifiers encode hardware constraints: 109 | 110 | | Qualifier | Meaning | Code Gen Implication | 111 | |-----------|---------|---------------------| 112 | | `__I` (volatile const) | Read-only register | Writes are UB, emit read-only access | 113 | | `__O` (volatile) | Write-only register | Reads return undefined, emit write-only | 114 | | `__IO` (volatile) | Read-write register | Normal volatile access | 115 | 116 | Writing to `IDR` (input register) or reading from `BSRR` (bit set/reset) is a hardware error. 117 | 118 | ## Microcontroller Memory Map Reality 119 | 120 | | Region | Address Range | Characteristics | 121 | |--------|---------------|-----------------| 122 | | Flash | `0x0800_0000` | Code + constants, read-only at runtime | 123 | | SRAM | `0x2000_0000` | Stack, heap, .bss, .data | 124 | | Peripherals | `0x4000_0000+` | Memory-mapped I/O, volatile, specific access widths | 125 | | System | `0xE000_0000` | NVIC, SysTick, debug - ARM core peripherals | 126 | 127 | ## Dependency: Farscape → BAREWire 128 | 129 | Farscape should take BAREWire as a dependency. The memory descriptor types live in BAREWire; Farscape populates them from parsed headers. 130 | 131 | This means BAREWire development may need to advance in parallel with Farscape to provide: 132 | - `PeripheralDescriptor` and related types 133 | - Memory region abstractions 134 | - Hardware address mapping primitives 135 | 136 | ## Pipeline Flow 137 | 138 | ``` 139 | CMSIS Header (.h) 140 | ↓ Farscape parses (clang JSON AST + macros) 141 | ↓ 142 | Farscape Output: 143 | ├── Types.fs (F# structs) 144 | ├── Bindings.fs (extern declarations) 145 | └── Descriptors (BAREWire memory catalog) 146 | ↓ 147 | PSG (contains type defs + extern markers + layout refs) 148 | ↓ 149 | Alex/Zipper traversal: 150 | ├── Peripheral access → MLIR volatile load/store 151 | ├── Layout info → correct offsets 152 | └── HAL functions → inline or linker symbol 153 | ↓ 154 | MLIR → LLVM → Native Binary 155 | ``` 156 | 157 | ## Tree-Shaking Drives Inclusion 158 | 159 | The massive CMSIS headers (20K+ lines, 15K+ macros) get tree-shaken to only what's used: 160 | 161 | 1. Reachability analysis identifies used peripherals/functions 162 | 2. Only referenced descriptors included in final artifact 163 | 3. Final binary is as tight as hand-written C 164 | 165 | ## Developer Experience 166 | 167 | 1. Add dependency in fidproj: `farscape-stm32l5 = { path = "..." }` 168 | 2. Write clean F# using typed peripheral access 169 | 3. Compile - Firefly handles all memory management 170 | 171 | The infrastructure is invisible unless the developer explicitly opts into manual control. 172 | -------------------------------------------------------------------------------- /src/Farscape.Core/CodeGenerator.fs: -------------------------------------------------------------------------------- 1 | namespace Farscape.Core 2 | 3 | open System 4 | open System.Text 5 | 6 | module CodeGenerator = 7 | type CodeSection = { 8 | FileName: string 9 | Content: string 10 | Order: int 11 | } 12 | 13 | type GeneratedCode = { 14 | Sections: CodeSection list 15 | } 16 | 17 | type BindingGeneratorOptions = { 18 | LibraryName: string 19 | Namespace: string 20 | IncludePaths: string list 21 | OutputDirectory: string 22 | Verbose: bool 23 | } 24 | 25 | type AdvancedBindingGenerator(options: BindingGeneratorOptions) = 26 | member _.GeneratePInvokeDeclarations(declarations: CppParser.Declaration list) = 27 | let sb = StringBuilder() 28 | sb.AppendLine($"module {options.Namespace}.NativeBindings") |> ignore 29 | sb.AppendLine("open System.Runtime.InteropServices") |> ignore 30 | sb.AppendLine() |> ignore 31 | 32 | // Filter and process function declarations 33 | let functions = 34 | declarations 35 | |> List.choose (function 36 | | CppParser.Declaration.Function f -> Some f 37 | | _ -> None) 38 | 39 | for func in functions do 40 | // Generate XML documentation 41 | match func.Documentation with 42 | | Some doc -> 43 | sb.AppendLine($"/// {doc}") |> ignore 44 | | None -> () 45 | 46 | // Generate P/Invoke with enhanced attributes 47 | let returnType = TypeMapper.getFSharpType func.ReturnType 48 | let parameters = 49 | func.Parameters 50 | |> List.map (fun (name, paramType) -> 51 | let fsharpType = TypeMapper.getFSharpType paramType 52 | $"{name}: {fsharpType}") 53 | |> String.concat ", " 54 | 55 | sb.AppendLine($"[]") |> ignore 56 | sb.AppendLine($"extern {returnType} {func.Name}({parameters})") |> ignore 57 | sb.AppendLine() |> ignore 58 | 59 | sb.ToString() 60 | 61 | member _.GenerateWrapperFunctions(declarations: CppParser.Declaration list) = 62 | let sb = StringBuilder() 63 | sb.AppendLine($"module {options.Namespace}.Wrappers") |> ignore 64 | sb.AppendLine("open System") |> ignore 65 | sb.AppendLine($"open {options.Namespace}.NativeBindings") |> ignore 66 | sb.AppendLine($"open {options.Namespace}.Types") |> ignore 67 | sb.AppendLine() |> ignore 68 | 69 | let functions = 70 | declarations 71 | |> List.choose (function 72 | | CppParser.Declaration.Function f -> Some f 73 | | _ -> None) 74 | 75 | for func in functions do 76 | let returnType = TypeMapper.getFSharpType func.ReturnType 77 | let parameters = 78 | func.Parameters 79 | |> List.map (fun (name, paramType) -> 80 | let fsharpType = TypeMapper.getFSharpType paramType 81 | $"{name}: {fsharpType}") 82 | |> String.concat ", " 83 | 84 | // Advanced error handling for integer return types 85 | sb.AppendLine($"/// Wrapped version of {func.Name} with enhanced error handling") |> ignore 86 | sb.AppendLine($"let wrap{func.Name} ({parameters}) : {returnType} =") |> ignore 87 | 88 | let paramList = String.concat ", " (func.Parameters |> List.map fst) 89 | if returnType = "int" || returnType = "int32" then 90 | sb.AppendLine($" let result = NativeBindings.{func.Name}({paramList})") |> ignore 91 | sb.AppendLine(" if result < 0 then") |> ignore 92 | sb.AppendLine(" failwith (\"Error in " + func.Name + ": \" + string result)") |> ignore 93 | sb.AppendLine(" result") |> ignore 94 | else 95 | sb.AppendLine($" NativeBindings.{func.Name}({paramList})") |> ignore 96 | 97 | sb.AppendLine() |> ignore 98 | 99 | sb.ToString() 100 | 101 | member _.GenerateStructWrappers(declarations: CppParser.Declaration list) = 102 | let sb = StringBuilder() 103 | sb.AppendLine($"module {options.Namespace}.StructWrappers") |> ignore 104 | sb.AppendLine("open System") |> ignore 105 | sb.AppendLine("open System.Runtime.InteropServices") |> ignore 106 | sb.AppendLine() |> ignore 107 | 108 | let structs = 109 | declarations 110 | |> List.choose (function 111 | | CppParser.Declaration.Struct s -> Some s 112 | | _ -> None) 113 | 114 | for struct' in structs do 115 | sb.AppendLine($"/// Wrapper for {struct'.Name} with memory management") |> ignore 116 | sb.AppendLine($"type {struct'.Name}Wrapper() =") |> ignore 117 | 118 | // Add fields 119 | for field in struct'.Fields do 120 | let fsharpType = TypeMapper.getFSharpType field.Type 121 | sb.AppendLine($" member val {field.Name}: {fsharpType} = Unchecked.defaultof<{fsharpType}> with get, set") |> ignore 122 | 123 | sb.AppendLine() |> ignore 124 | 125 | // Add native memory conversion methods 126 | sb.AppendLine(" /// Convert to native memory") |> ignore 127 | sb.AppendLine(" member this.ToNativeMemory() =") |> ignore 128 | sb.AppendLine(" let size = Marshal.SizeOf(this)") |> ignore 129 | sb.AppendLine(" let ptr = Marshal.AllocHGlobal(size)") |> ignore 130 | sb.AppendLine(" Marshal.StructureToPtr(this, ptr, false)") |> ignore 131 | sb.AppendLine(" ptr") |> ignore 132 | sb.AppendLine() |> ignore 133 | 134 | sb.AppendLine(" /// Create from native memory") |> ignore 135 | sb.AppendLine($" static member FromNativeMemory(ptr: nativeint) =") |> ignore 136 | sb.AppendLine($" Marshal.PtrToStructure<{struct'.Name}Wrapper>(ptr)") |> ignore 137 | 138 | sb.AppendLine() |> ignore 139 | 140 | sb.ToString() 141 | 142 | member this.GenerateBindings(declarations: CppParser.Declaration list) = 143 | [ 144 | { 145 | FileName = "NativeBindings.fs" 146 | Content = this.GeneratePInvokeDeclarations(declarations) 147 | Order = 1 148 | } 149 | { 150 | FileName = "Wrappers.fs" 151 | Content = this.GenerateWrapperFunctions(declarations) 152 | Order = 2 153 | } 154 | { 155 | FileName = "StructWrappers.fs" 156 | Content = this.GenerateStructWrappers(declarations) 157 | Order = 3 158 | } 159 | ] 160 | 161 | /// Generate code from declarations 162 | let generateCode (declarations: CppParser.Declaration list) (namespace': string) (libraryName: string) = 163 | let options: BindingGeneratorOptions = { 164 | LibraryName = libraryName 165 | Namespace = namespace' 166 | IncludePaths = [] 167 | OutputDirectory = "" 168 | Verbose = false 169 | } 170 | 171 | let generator = AdvancedBindingGenerator(options) 172 | let sections = generator.GenerateBindings(declarations) 173 | 174 | { Sections = sections } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Farscape Documentation 2 | 3 | ## Overview 4 | 5 | Farscape is the C/C++ binding generator for the Fidelity native F# compilation ecosystem. It parses C/C++ headers and generates type-safe F# bindings along with BAREWire memory descriptors for hardware targets. 6 | 7 | ## Table of Contents 8 | 9 | ### Architecture 10 | 11 | 1. [Architecture Overview](./01_Architecture_Overview.md) - Pipeline structure and design principles 12 | 2. [BAREWire Integration](./02_BAREWire_Integration.md) - Hardware descriptor generation 13 | 3. [fsnative Integration](./03_fsnative_Integration.md) - Native type system coordination 14 | 15 | ### Reference 16 | 17 | - [Type Mapping Reference](./Type_Mapping_Reference.md) - C to F# type mappings 18 | - [CMSIS HAL Guide](./CMSIS_HAL_Guide.md) - STM32 and ARM peripheral bindings 19 | 20 | ## Position in Fidelity Ecosystem 21 | 22 | Farscape sits at a critical junction in the Fidelity compilation pipeline: 23 | 24 | ``` 25 | ┌─────────────────────────────────────────────────────────────────────────┐ 26 | │ Farscape in Fidelity Ecosystem │ 27 | │ │ 28 | │ ┌─────────────┐ │ 29 | │ │ C/C++ │ CMSIS headers, HAL libraries, vendor SDKs │ 30 | │ │ Headers │ │ 31 | │ └──────┬──────┘ │ 32 | │ │ │ 33 | │ ▼ │ 34 | │ ┌─────────────────────────────────────────────────────────────────┐ │ 35 | │ │ Farscape │ │ 36 | │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ 37 | │ │ │ XParsec │──▶│ TypeMapper│──▶│ CodeGen │ │ │ 38 | │ │ │ Parser │ │ │ │ │ │ │ 39 | │ │ └───────────┘ └───────────┘ └───────────┘ │ │ 40 | │ │ │ │ │ │ │ 41 | │ │ │ Uses fsnative Uses BAREWire │ │ 42 | │ │ │ types descriptors │ │ 43 | │ └────────┼───────────────┼───────────────┼─────────────────────────┘ │ 44 | │ │ │ │ │ 45 | │ ▼ ▼ ▼ │ 46 | │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 47 | │ │ Types.fs │ │ Bindings.fs │ │ Descriptors │ │ 48 | │ │ (F# structs)│ │ (externs) │ │ (BAREWire) │ │ 49 | │ └─────────────┘ └─────────────┘ └─────────────┘ │ 50 | │ │ │ │ │ 51 | │ └─────────────────┴─────────────────┘ │ 52 | │ │ │ 53 | │ ▼ │ 54 | │ ┌─────────────────────────────────────────────────────────────────┐ │ 55 | │ │ Firefly/Alex │ │ 56 | │ │ Consumes F# bindings and descriptors to generate native code │ │ 57 | │ └─────────────────────────────────────────────────────────────────┘ │ 58 | │ │ │ 59 | │ ▼ │ 60 | │ Native Binary │ 61 | └─────────────────────────────────────────────────────────────────────────┘ 62 | ``` 63 | 64 | ## Dependencies 65 | 66 | Farscape has two critical dependencies that must advance in lockstep: 67 | 68 | ### 1. fsnative (Type Provider) 69 | 70 | fsnative provides the phantom type measures that make Farscape's output type-safe: 71 | 72 | ```fsharp 73 | // fsnative provides 74 | [] type peripheral 75 | [] type readOnly 76 | [] type writeOnly 77 | [] type readWrite 78 | 79 | type NativePtr<'T, [] 'region, [] 'access> 80 | 81 | // Farscape generates using these types 82 | type GPIO_TypeDef = { 83 | IDR: NativePtr 84 | ODR: NativePtr 85 | BSRR: NativePtr 86 | } 87 | ``` 88 | 89 | **Without fsnative types**, Farscape can only generate untyped `nativeint` pointers. 90 | 91 | ### 2. BAREWire (Descriptor Types) 92 | 93 | BAREWire provides the hardware descriptor types that Farscape populates: 94 | 95 | ```fsharp 96 | // BAREWire provides 97 | type PeripheralDescriptor = { ... } 98 | type FieldDescriptor = { ... } 99 | type AccessKind = ReadOnly | WriteOnly | ReadWrite 100 | type MemoryRegionKind = Peripheral | SRAM | Flash | ... 101 | 102 | // Farscape populates these from parsed headers 103 | let gpioDescriptor = { 104 | Name = "GPIO" 105 | Instances = Map.ofList ["GPIOA", 0x48000000un; ...] 106 | Layout = { Fields = [...] } 107 | MemoryRegion = Peripheral 108 | } 109 | ``` 110 | 111 | **Without BAREWire types**, Farscape cannot generate the memory catalog that Alex needs. 112 | 113 | ## The Interlock Requirement 114 | 115 | The dependency chain must be maintained: 116 | 117 | ``` 118 | FSharp.UMX ──absorption──▶ fsnative ──types──▶ Farscape ──uses──▶ BAREWire 119 | ``` 120 | 121 | See [Memory Interlock Requirements](https://github.com/speakeztech/firefly/docs/Memory_Interlock_Requirements.md) for full details. 122 | 123 | ## Output Modes 124 | 125 | Farscape supports three output modes: 126 | 127 | ### 1. F# Bindings (`--output-mode bindings`) 128 | 129 | Standard F# type definitions and extern declarations: 130 | 131 | ```fsharp 132 | [] 133 | type GPIO_InitTypeDef = { 134 | Pin: uint32 135 | Mode: uint32 136 | Pull: uint32 137 | Speed: uint32 138 | } 139 | 140 | module Platform.Bindings = 141 | let halGpioInit gpio init : unit = Unchecked.defaultof 142 | ``` 143 | 144 | ### 2. BAREWire Descriptors (`--output-mode descriptors`) 145 | 146 | Hardware memory catalog for Alex: 147 | 148 | ```fsharp 149 | let gpioDescriptor: PeripheralDescriptor = { 150 | Name = "GPIO" 151 | Instances = Map.ofList [ 152 | "GPIOA", 0x48000000un 153 | "GPIOB", 0x48000400un 154 | ] 155 | Layout = { 156 | Fields = [ 157 | { Name = "MODER"; Offset = 0x00; Type = U32; Access = ReadWrite } 158 | { Name = "IDR"; Offset = 0x10; Type = U32; Access = ReadOnly } 159 | { Name = "BSRR"; Offset = 0x18; Type = U32; Access = WriteOnly } 160 | ] 161 | } 162 | MemoryRegion = Peripheral 163 | } 164 | ``` 165 | 166 | ### 3. Both (`--output-mode both`) 167 | 168 | Complete output for Fidelity integration. 169 | 170 | ## CMSIS Qualifier Handling 171 | 172 | Farscape extracts access constraints from CMSIS `__I`, `__O`, `__IO` qualifiers: 173 | 174 | | CMSIS | C Definition | Generated AccessKind | 175 | |-------|--------------|---------------------| 176 | | `__I` | `volatile const` | `ReadOnly` | 177 | | `__O` | `volatile` | `WriteOnly` | 178 | | `__IO` | `volatile` | `ReadWrite` | 179 | 180 | These map to fsnative measures: 181 | - `__I` → `readOnly` measure 182 | - `__O` → `writeOnly` measure 183 | - `__IO` → `readWrite` measure 184 | 185 | ## Related Documentation 186 | 187 | | Document | Location | 188 | |----------|----------| 189 | | BAREWire Hardware Descriptors | `~/repos/BAREWire/docs/08 Hardware Descriptors.md` | 190 | | fsnative Specification | `~/repos/fsnative-spec/docs/fidelity/FNCS_Specification.md` | 191 | | UMX Integration Plan | `~/repos/FSharp.UMX/docs/fidelity/UMX_Integration_Plan.md` | 192 | | Memory Interlock Requirements | `~/repos/Firefly/docs/Memory_Interlock_Requirements.md` | 193 | | Staged Memory Model | `~/repos/Firefly/docs/Staged_Memory_Model.md` | 194 | 195 | ## Development Status 196 | 197 | Current implementation status: 198 | 199 | - [x] XParsec-based C header parsing (basic) 200 | - [x] Basic type mapping (primitives, pointers, structs) 201 | - [x] F# binding generation 202 | - [ ] CppParser wired to XParsec (currently hardcoded to cJSON.h) 203 | - [ ] Macro/constant extraction (#define values) 204 | - [ ] CMSIS qualifier extraction (__I, __O, __IO) 205 | - [ ] BAREWire descriptor generation (awaiting BAREWire types) 206 | - [ ] fsnative type integration (awaiting fsnative maturation) 207 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from `dotnet new gitignore` 5 | 6 | # Farscape.Cli specific output 7 | src/Farscape.*/output/ 8 | src/Farscape.*/bin/ 9 | src/Farscape.*/obj/ 10 | src/Farscape.*/**/bin/ 11 | src/Farscape.*/**/obj/ 12 | 13 | # This ensures all generated files in output are ignored 14 | src/Farscape.Cli/output/**/* 15 | 16 | *.exe 17 | 18 | # dotenv files 19 | .env 20 | 21 | # User-specific files 22 | *.rsuser 23 | *.suo 24 | *.user 25 | *.userosscache 26 | *.sln.docstates 27 | 28 | # User-specific files (MonoDevelop/Xamarin Studio) 29 | *.userprefs 30 | 31 | # Mono auto generated files 32 | mono_crash.* 33 | # parser output 34 | output 35 | # Build results 36 | [Dd]ebug/ 37 | [Dd]ebugPublic/ 38 | [Rr]elease/ 39 | [Rr]eleases/ 40 | x64/ 41 | x86/ 42 | [Ww][Ii][Nn]32/ 43 | [Aa][Rr][Mm]/ 44 | [Aa][Rr][Mm]64/ 45 | bld/ 46 | [Bb]in/ 47 | [Oo]bj/ 48 | [Ll]og/ 49 | [Ll]ogs/ 50 | 51 | # Visual Studio 2015/2017 cache/options directory 52 | .vs/ 53 | .idea/ 54 | # Uncomment if you have tasks that create the project's static files in wwwroot 55 | #wwwroot/ 56 | 57 | # Visual Studio 2017 auto generated files 58 | Generated\ Files/ 59 | 60 | # MSTest test Results 61 | [Tt]est[Rr]esult*/ 62 | [Bb]uild[Ll]og.* 63 | 64 | # NUnit 65 | *.VisualState.xml 66 | TestResult.xml 67 | nunit-*.xml 68 | 69 | # Build Results of an ATL Project 70 | [Dd]ebugPS/ 71 | [Rr]eleasePS/ 72 | dlldata.c 73 | 74 | # Benchmark Results 75 | BenchmarkDotNet.Artifacts/ 76 | 77 | # .NET 78 | project.lock.json 79 | project.fragment.lock.json 80 | artifacts/ 81 | 82 | # Tye 83 | .tye/ 84 | 85 | # ASP.NET Scaffolding 86 | ScaffoldingReadMe.txt 87 | 88 | # StyleCop 89 | StyleCopReport.xml 90 | 91 | # Files built by Visual Studio 92 | *_i.c 93 | *_p.c 94 | *_h.h 95 | *.ilk 96 | *.meta 97 | *.obj 98 | *.iobj 99 | *.pch 100 | *.pdb 101 | *.ipdb 102 | *.pgc 103 | *.pgd 104 | *.rsp 105 | *.sbr 106 | *.tlb 107 | *.tli 108 | *.tlh 109 | *.tmp 110 | *.tmp_proj 111 | *_wpftmp.csproj 112 | *.log 113 | *.tlog 114 | *.vspscc 115 | *.vssscc 116 | .builds 117 | *.pidb 118 | *.svclog 119 | *.scc 120 | 121 | # Chutzpah Test files 122 | _Chutzpah* 123 | 124 | # Visual C++ cache files 125 | ipch/ 126 | *.aps 127 | *.ncb 128 | *.opendb 129 | *.opensdf 130 | *.sdf 131 | *.cachefile 132 | *.VC.db 133 | *.VC.VC.opendb 134 | 135 | # Visual Studio profiler 136 | *.psess 137 | *.vsp 138 | *.vspx 139 | *.sap 140 | 141 | # Visual Studio Trace Files 142 | *.e2e 143 | 144 | # TFS 2012 Local Workspace 145 | $tf/ 146 | 147 | # Guidance Automation Toolkit 148 | *.gpState 149 | 150 | # ReSharper is a .NET coding add-in 151 | _ReSharper*/ 152 | *.[Rr]e[Ss]harper 153 | *.DotSettings.user 154 | 155 | # TeamCity is a build add-in 156 | _TeamCity* 157 | 158 | # DotCover is a Code Coverage Tool 159 | *.dotCover 160 | 161 | # AxoCover is a Code Coverage Tool 162 | .axoCover/* 163 | !.axoCover/settings.json 164 | 165 | # Coverlet is a free, cross platform Code Coverage Tool 166 | coverage*.json 167 | coverage*.xml 168 | coverage*.info 169 | 170 | # Visual Studio code coverage results 171 | *.coverage 172 | *.coveragexml 173 | 174 | # NCrunch 175 | _NCrunch_* 176 | .*crunch*.local.xml 177 | nCrunchTemp_* 178 | 179 | # MightyMoose 180 | *.mm.* 181 | AutoTest.Net/ 182 | 183 | # Web workbench (sass) 184 | .sass-cache/ 185 | 186 | # Installshield output folder 187 | [Ee]xpress/ 188 | 189 | # DocProject is a documentation generator add-in 190 | DocProject/buildhelp/ 191 | DocProject/Help/*.HxT 192 | DocProject/Help/*.HxC 193 | DocProject/Help/*.hhc 194 | DocProject/Help/*.hhk 195 | DocProject/Help/*.hhp 196 | DocProject/Help/Html2 197 | DocProject/Help/html 198 | 199 | # Click-Once directory 200 | publish/ 201 | 202 | # Publish Web Output 203 | *.[Pp]ublish.xml 204 | *.azurePubxml 205 | # Note: Comment the next line if you want to checkin your web deploy settings, 206 | # but database connection strings (with potential passwords) will be unencrypted 207 | *.pubxml 208 | *.publishproj 209 | 210 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 211 | # checkin your Azure Web App publish settings, but sensitive information contained 212 | # in these scripts will be unencrypted 213 | PublishScripts/ 214 | 215 | # NuGet Packages 216 | *.nupkg 217 | # NuGet Symbol Packages 218 | *.snupkg 219 | # The packages folder can be ignored because of Package Restore 220 | **/[Pp]ackages/* 221 | # except build/, which is used as an MSBuild target. 222 | !**/[Pp]ackages/build/ 223 | # Uncomment if necessary however generally it will be regenerated when needed 224 | #!**/[Pp]ackages/repositories.config 225 | # NuGet v3's project.json files produces more ignorable files 226 | *.nuget.props 227 | *.nuget.targets 228 | 229 | # Microsoft Azure Build Output 230 | csx/ 231 | *.build.csdef 232 | 233 | # Microsoft Azure Emulator 234 | ecf/ 235 | rcf/ 236 | 237 | # Windows Store app package directories and files 238 | AppPackages/ 239 | BundleArtifacts/ 240 | Package.StoreAssociation.xml 241 | _pkginfo.txt 242 | *.appx 243 | *.appxbundle 244 | *.appxupload 245 | 246 | # Visual Studio cache files 247 | # files ending in .cache can be ignored 248 | *.[Cc]ache 249 | # but keep track of directories ending in .cache 250 | !?*.[Cc]ache/ 251 | 252 | # Others 253 | ClientBin/ 254 | ~$* 255 | *~ 256 | *.dbmdl 257 | *.dbproj.schemaview 258 | *.jfm 259 | *.pfx 260 | *.publishsettings 261 | orleans.codegen.cs 262 | 263 | # Including strong name files can present a security risk 264 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 265 | #*.snk 266 | 267 | # Since there are multiple workflows, uncomment next line to ignore bower_components 268 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 269 | #bower_components/ 270 | 271 | # RIA/Silverlight projects 272 | Generated_Code/ 273 | 274 | # Backup & report files from converting an old project file 275 | # to a newer Visual Studio version. Backup files are not needed, 276 | # because we have git ;-) 277 | _UpgradeReport_Files/ 278 | Backup*/ 279 | UpgradeLog*.XML 280 | UpgradeLog*.htm 281 | ServiceFabricBackup/ 282 | *.rptproj.bak 283 | 284 | # SQL Server files 285 | *.mdf 286 | *.ldf 287 | *.ndf 288 | 289 | # Business Intelligence projects 290 | *.rdl.data 291 | *.bim.layout 292 | *.bim_*.settings 293 | *.rptproj.rsuser 294 | *- [Bb]ackup.rdl 295 | *- [Bb]ackup ([0-9]).rdl 296 | *- [Bb]ackup ([0-9][0-9]).rdl 297 | 298 | # Microsoft Fakes 299 | FakesAssemblies/ 300 | 301 | # GhostDoc plugin setting file 302 | *.GhostDoc.xml 303 | 304 | # Node.js Tools for Visual Studio 305 | .ntvs_analysis.dat 306 | node_modules/ 307 | 308 | # Visual Studio 6 build log 309 | *.plg 310 | 311 | # Visual Studio 6 workspace options file 312 | *.opt 313 | 314 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 315 | *.vbw 316 | 317 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 318 | *.vbp 319 | 320 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 321 | *.dsw 322 | *.dsp 323 | 324 | # Visual Studio 6 technical files 325 | *.ncb 326 | *.aps 327 | 328 | # Visual Studio LightSwitch build output 329 | **/*.HTMLClient/GeneratedArtifacts 330 | **/*.DesktopClient/GeneratedArtifacts 331 | **/*.DesktopClient/ModelManifest.xml 332 | **/*.Server/GeneratedArtifacts 333 | **/*.Server/ModelManifest.xml 334 | _Pvt_Extensions 335 | 336 | # Paket dependency manager 337 | .paket/paket.exe 338 | paket-files/ 339 | 340 | # FAKE - F# Make 341 | .fake/ 342 | 343 | # CodeRush personal settings 344 | .cr/personal 345 | 346 | # Python Tools for Visual Studio (PTVS) 347 | __pycache__/ 348 | *.pyc 349 | 350 | # Cake - Uncomment if you are using it 351 | # tools/** 352 | # !tools/packages.config 353 | 354 | # Tabs Studio 355 | *.tss 356 | 357 | # Telerik's JustMock configuration file 358 | *.jmconfig 359 | 360 | # BizTalk build output 361 | *.btp.cs 362 | *.btm.cs 363 | *.odx.cs 364 | *.xsd.cs 365 | 366 | # OpenCover UI analysis results 367 | OpenCover/ 368 | 369 | # Azure Stream Analytics local run output 370 | ASALocalRun/ 371 | 372 | # MSBuild Binary and Structured Log 373 | *.binlog 374 | 375 | # NVidia Nsight GPU debugger configuration file 376 | *.nvuser 377 | 378 | # MFractors (Xamarin productivity tool) working folder 379 | .mfractor/ 380 | 381 | # Local History for Visual Studio 382 | .localhistory/ 383 | 384 | # Visual Studio History (VSHistory) files 385 | .vshistory/ 386 | 387 | # BeatPulse healthcheck temp database 388 | healthchecksdb 389 | 390 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 391 | MigrationBackup/ 392 | 393 | # Ionide (cross platform F# VS Code tools) working folder 394 | .ionide/ 395 | 396 | # Fody - auto-generated XML schema 397 | FodyWeavers.xsd 398 | 399 | # VS Code files for those working on multiple tools 400 | .vscode/* 401 | !.vscode/settings.json 402 | !.vscode/tasks.json 403 | !.vscode/launch.json 404 | !.vscode/extensions.json 405 | *.code-workspace 406 | 407 | # Local History for Visual Studio Code 408 | .history/ 409 | 410 | # Windows Installer files from build outputs 411 | *.cab 412 | *.msi 413 | *.msix 414 | *.msm 415 | *.msp 416 | 417 | # JetBrains Rider 418 | *.sln.iml 419 | .idea/ 420 | 421 | ## 422 | ## Visual studio for Mac 423 | ## 424 | 425 | 426 | # globs 427 | Makefile.in 428 | *.userprefs 429 | *.usertasks 430 | config.make 431 | config.status 432 | aclocal.m4 433 | install-sh 434 | autom4te.cache/ 435 | *.tar.gz 436 | tarballs/ 437 | test-results/ 438 | 439 | # Mac bundle stuff 440 | *.dmg 441 | *.app 442 | 443 | # content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore 444 | # General 445 | .DS_Store 446 | .AppleDouble 447 | .LSOverride 448 | 449 | # Icon must end with two \r 450 | Icon 451 | 452 | 453 | # Thumbnails 454 | ._* 455 | 456 | # Files that might appear in the root of a volume 457 | .DocumentRevisions-V100 458 | .fseventsd 459 | .Spotlight-V100 460 | .TemporaryItems 461 | .Trashes 462 | .VolumeIcon.icns 463 | .com.apple.timemachine.donotpresent 464 | 465 | # Directories potentially created on remote AFP share 466 | .AppleDB 467 | .AppleDesktop 468 | Network Trash Folder 469 | Temporary Items 470 | .apdisk 471 | 472 | # content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore 473 | # Windows thumbnail cache files 474 | Thumbs.db 475 | ehthumbs.db 476 | ehthumbs_vista.db 477 | 478 | # Dump file 479 | *.stackdump 480 | 481 | # Folder config file 482 | [Dd]esktop.ini 483 | 484 | # Recycle Bin used on file shares 485 | $RECYCLE.BIN/ 486 | 487 | # Windows Installer files 488 | *.cab 489 | *.msi 490 | *.msix 491 | *.msm 492 | *.msp 493 | 494 | # Windows shortcuts 495 | *.lnk 496 | 497 | # Vim temporary swap files 498 | *.swp 499 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DUAL LICENSING NOTICE FOR FARSCAPE 2 | =================================== 3 | 4 | Copyright 2025 SpeakEZ Technologies, Inc. 5 | 6 | Farscape is dual-licensed under both: 7 | 8 | 1. The Apache License, Version 2.0 (below) - for non-commercial, educational, 9 | and open source use. 10 | 11 | 2. A Commercial License - for commercial and enterprise applications. 12 | 13 | Farscape is part of the Fidelity Framework, which includes technology covered by 14 | U.S. Patent Application No. 63/786,247. For patent licensing information, please 15 | refer to the PATENTS.md file included with this software. 16 | 17 | For information about obtaining a Commercial License, please refer to the 18 | Commercial.md file included with this software or contact: 19 | 20 | SpeakEZ Technologies, Inc. 21 | https://speakez.tech/contact 22 | 23 | ========================================================================= 24 | 25 | Apache License 26 | Version 2.0, January 2004 27 | http://www.apache.org/licenses/ 28 | 29 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 30 | 31 | 1. Definitions. 32 | 33 | "License" shall mean the terms and conditions for use, reproduction, 34 | and distribution as defined by Sections 1 through 9 of this document. 35 | 36 | "Licensor" shall mean the copyright owner or entity authorized by 37 | the copyright owner that is granting the License. 38 | 39 | "Legal Entity" shall mean the union of the acting entity and all 40 | other entities that control, are controlled by, or are under common 41 | control with that entity. For the purposes of this definition, 42 | "control" means (i) the power, direct or indirect, to cause the 43 | direction or management of such entity, whether by contract or 44 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 45 | outstanding shares, or (iii) beneficial ownership of such entity. 46 | 47 | "You" (or "Your") shall mean an individual or Legal Entity 48 | exercising permissions granted by this License. 49 | 50 | "Source" form shall mean the preferred form for making modifications, 51 | including but not limited to software source code, documentation 52 | source, and configuration files. 53 | 54 | "Object" form shall mean any form resulting from mechanical 55 | transformation or translation of a Source form, including but 56 | not limited to compiled object code, generated documentation, 57 | and conversions to other media types. 58 | 59 | "Work" shall mean the authorship work, whether in Source or 60 | Object form, made available under the License, as indicated by a 61 | copyright notice that is included in or attached to the work 62 | (an example is provided in the Appendix below). 63 | 64 | "Derivative Works" shall mean any work, whether in Source or Object 65 | form, that is based on (or derived from) the Work and for which the 66 | editorial revisions, annotations, elaborations, or other modifications 67 | represent, as a whole, an original work of authorship. For the purposes 68 | of this License, Derivative Works shall not include works that remain 69 | separable from, or merely link (or bind by name) to the interfaces of, 70 | the Work and Derivative Works thereof. 71 | 72 | "Contribution" shall mean any work of authorship, including 73 | the original version of the Work and any modifications or additions 74 | to that Work or Derivative Works thereof, that is intentionally 75 | submitted to Licensor for inclusion in the Work by the copyright owner 76 | or by an individual or Legal Entity authorized to submit on behalf of 77 | the copyright owner. For the purposes of this definition, "submitted" 78 | means any form of electronic, verbal, or written communication sent 79 | to the Licensor or its representatives, including but not limited to 80 | communication on electronic mailing lists, source code control systems, 81 | and issue tracking systems that are managed by, or on behalf of, the 82 | Licensor for the purpose of discussing and improving the Work, but 83 | excluding communication that is conspicuously marked or otherwise 84 | designated in writing by the copyright owner as "Not a Contribution." 85 | 86 | "Contributor" shall mean Licensor and any individual or Legal Entity 87 | on behalf of whom a Contribution has been received by Licensor and 88 | subsequently incorporated within the Work. 89 | 90 | 2. Grant of Copyright License. Subject to the terms and conditions of 91 | this License, each Contributor hereby grants to You a perpetual, 92 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 93 | copyright license to reproduce, prepare Derivative Works of, 94 | publicly display, publicly perform, sublicense, and distribute the 95 | Work and such Derivative Works in Source or Object form. 96 | 97 | 3. Grant of Patent License. Subject to the terms and conditions of 98 | this License, each Contributor hereby grants to You a perpetual, 99 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 100 | (except as stated in this section) patent license to make, have made, 101 | use, offer to sell, sell, import, and otherwise transfer the Work, 102 | where such license applies only to those patent claims licensable 103 | by such Contributor that are necessarily infringed by their 104 | Contribution(s) alone or by combination of their Contribution(s) 105 | with the Work to which such Contribution(s) was submitted. If You 106 | institute patent litigation against any entity (including a 107 | cross-claim or counterclaim in a lawsuit) alleging that the Work 108 | or a Contribution incorporated within the Work constitutes direct 109 | or contributory patent infringement, then any patent licenses 110 | granted to You under this License for that Work shall terminate 111 | as of the date such litigation is filed. 112 | 113 | 4. Redistribution. You may reproduce and distribute copies of the 114 | Work or Derivative Works thereof in any medium, with or without 115 | modifications, and in Source or Object form, provided that You 116 | meet the following conditions: 117 | 118 | (a) You must give any other recipients of the Work or 119 | Derivative Works a copy of this License; and 120 | 121 | (b) You must cause any modified files to carry prominent notices 122 | stating that You changed the files; and 123 | 124 | (c) You must retain, in the Source form of any Derivative Works 125 | that You distribute, all copyright, patent, trademark, and 126 | attribution notices from the Source form of the Work, 127 | excluding those notices that do not pertain to any part of 128 | the Derivative Works; and 129 | 130 | (d) If the Work includes a "NOTICE" text file as part of its 131 | distribution, then any Derivative Works that You distribute must 132 | include a readable copy of the attribution notices contained 133 | within such NOTICE file, excluding those notices that do not 134 | pertain to any part of the Derivative Works, in at least one 135 | of the following places: within a NOTICE text file distributed 136 | as part of the Derivative Works; within the Source form or 137 | documentation, if provided along with the Derivative Works; or, 138 | within a display generated by the Derivative Works, if and 139 | wherever such third-party notices normally appear. The contents 140 | of the NOTICE file are for informational purposes only and 141 | do not modify the License. You may add Your own attribution 142 | notices within Derivative Works that You distribute, alongside 143 | or as an addendum to the NOTICE text from the Work, provided 144 | that such additional attribution notices cannot be construed 145 | as modifying the License. 146 | 147 | You may add Your own copyright statement to Your modifications and 148 | may provide additional or different license terms and conditions 149 | for use, reproduction, or distribution of Your modifications, or 150 | for any such Derivative Works as a whole, provided Your use, 151 | reproduction, and distribution of the Work otherwise complies with 152 | the conditions stated in this License. 153 | 154 | 5. Submission of Contributions. Unless You explicitly state otherwise, 155 | any Contribution intentionally submitted for inclusion in the Work 156 | by You to the Licensor shall be under the terms and conditions of 157 | this License, without any additional terms or conditions. 158 | Notwithstanding the above, nothing herein shall supersede or modify 159 | the terms of any separate license agreement you may have executed 160 | with Licensor regarding such Contributions. 161 | 162 | 6. Trademarks. This License does not grant permission to use the trade 163 | names, trademarks, service marks, or product names of the Licensor, 164 | except as required for reasonable and customary use in describing the 165 | origin of the Work and reproducing the content of the NOTICE file. 166 | 167 | 7. Disclaimer of Warranty. Unless required by applicable law or 168 | agreed to in writing, Licensor provides the Work (and each 169 | Contributor provides its Contributions) on an "AS IS" BASIS, 170 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 171 | implied, including, without limitation, any warranties or conditions 172 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 173 | PARTICULAR PURPOSE. You are solely responsible for determining the 174 | appropriateness of using or redistributing the Work and assume any 175 | risks associated with Your exercise of permissions under this License. 176 | 177 | 8. Limitation of Liability. In no event and under no legal theory, 178 | whether in tort (including negligence), contract, or otherwise, 179 | unless required by applicable law (such as deliberate and grossly 180 | negligent acts) or agreed to in writing, shall any Contributor be 181 | liable to You for damages, including any direct, indirect, special, 182 | incidental, or consequential damages of any character arising as a 183 | result of this License or out of the use or inability to use the 184 | Work (including but not limited to damages for loss of goodwill, 185 | work stoppage, computer failure or malfunction, or any and all 186 | other commercial damages or losses), even if such Contributor 187 | has been advised of the possibility of such damages. 188 | 189 | 9. Accepting Warranty or Additional Liability. While redistributing 190 | the Work or Derivative Works thereof, You may choose to offer, 191 | and charge a fee for, acceptance of support, warranty, indemnity, 192 | or other liability obligations and/or rights consistent with this 193 | License. However, in accepting such obligations, You may act only 194 | on Your own behalf and on Your sole responsibility, not on behalf 195 | of any other Contributor, and only if You agree to indemnify, 196 | defend, and hold each Contributor harmless for any liability 197 | incurred by, or claims asserted against, such Contributor by reason 198 | of your accepting any such warranty or additional liability. 199 | 200 | END OF TERMS AND CONDITIONS 201 | -------------------------------------------------------------------------------- /docs/01_Architecture_Overview.md: -------------------------------------------------------------------------------- 1 | # Farscape Architecture Overview 2 | 3 | ## Purpose 4 | 5 | Farscape generates F# bindings from C/C++ headers for the Fidelity native compilation ecosystem. Unlike traditional FFI tools that target runtime interop, Farscape generates code specifically for ahead-of-time native compilation via Firefly. 6 | 7 | > **Architecture Update (December 2024)**: Farscape now generates **quotation-based output** with active patterns. 8 | > See `~/repos/Firefly/docs/Quotation_Based_Memory_Architecture.md` for the unified four-component architecture. 9 | 10 | ## Design Principles 11 | 12 | ### 1. XParsec-Powered Parsing 13 | 14 | Farscape uses XParsec, the same parser combinator library that powers Firefly's PSG traversal. This provides: 15 | 16 | - Type-safe parsing with compile-time guarantees 17 | - Composable parsers for complex C/C++ constructs 18 | - Excellent error messages with precise locations 19 | - Pure F# implementation with no external dependencies 20 | 21 | ### 2. Fidelity-First Output 22 | 23 | Output is designed for Firefly native compilation, not .NET runtime interop: 24 | 25 | - Uses fsnative phantom types for memory safety 26 | - Generates BAREWire descriptors for hardware targets 27 | - Produces `Platform.Bindings` declarations that Alex recognizes 28 | - No BCL dependencies in generated code 29 | 30 | ### 3. Embedded Focus 31 | 32 | First-class support for embedded targets: 33 | 34 | - CMSIS HAL header parsing 35 | - Peripheral register extraction 36 | - Volatile qualifier preservation 37 | - Memory-mapped I/O patterns 38 | 39 | ## Pipeline Architecture 40 | 41 | ``` 42 | ┌─────────────────────────────────────────────────────────────────────────┐ 43 | │ Farscape Pipeline │ 44 | │ │ 45 | │ Stage 1: Parsing │ 46 | │ ┌───────────────────────────────────────────────────────────────────┐ │ 47 | │ │ HeaderParser.fs (XParsec) │ │ 48 | │ │ - C/C++ lexical analysis │ │ 49 | │ │ - Declaration extraction │ │ 50 | │ │ - Macro preprocessing (future) │ │ 51 | │ └───────────────────────────────────────────────────────────────────┘ │ 52 | │ │ │ 53 | │ ▼ │ 54 | │ Stage 2: Type Mapping │ 55 | │ ┌───────────────────────────────────────────────────────────────────┐ │ 56 | │ │ TypeMapper.fs │ │ 57 | │ │ - C type → F# type conversion │ │ 58 | │ │ - Pointer handling (const, volatile) │ │ 59 | │ │ - Struct field layout │ │ 60 | │ └───────────────────────────────────────────────────────────────────┘ │ 61 | │ │ │ 62 | │ ▼ │ 63 | │ Stage 3: Code Generation │ 64 | │ ┌───────────────────────────────────────────────────────────────────┐ │ 65 | │ │ CodeGenerator.fs │ │ 66 | │ │ - F# type definitions │ │ 67 | │ │ - Platform.Bindings declarations │ │ 68 | │ │ - Module structure │ │ 69 | │ └───────────────────────────────────────────────────────────────────┘ │ 70 | │ │ │ 71 | │ ▼ │ 72 | │ Stage 4: Quotation & Pattern Generation │ 73 | │ ┌───────────────────────────────────────────────────────────────────┐ │ 74 | │ │ QuotationGenerator.fs │ │ 75 | │ │ - Expr quotations │ │ 76 | │ │ - Active patterns for PSG recognition │ │ 77 | │ │ - MemoryModel record generation │ │ 78 | │ └───────────────────────────────────────────────────────────────────┘ │ 79 | │ │ │ 80 | │ ▼ │ 81 | │ Output │ 82 | │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 83 | │ │ Types.fs │ │ Bindings.fs │ │Quotations.fs│ │ Patterns.fs │ │ 84 | │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ 85 | └─────────────────────────────────────────────────────────────────────────┘ 86 | ``` 87 | 88 | ## Core Modules 89 | 90 | ### HeaderParser.fs 91 | 92 | XParsec-based parser for C/C++ headers: 93 | 94 | ```fsharp 95 | // Parse a C struct definition 96 | let structParser = 97 | parse { 98 | do! keyword "struct" 99 | let! name = optional identifier 100 | do! symbol "{" 101 | let! fields = many fieldParser 102 | do! symbol "}" 103 | return StructDef { Name = name; Fields = fields } 104 | } 105 | 106 | // Parse a field with qualifiers 107 | let fieldParser = 108 | parse { 109 | let! qualifiers = many qualifier // volatile, const, __I, __O, __IO 110 | let! baseType = typeSpecifier 111 | let! name = identifier 112 | let! arraySize = optional arrayDeclarator 113 | do! symbol ";" 114 | return { Qualifiers = qualifiers; Type = baseType; Name = name; ArraySize = arraySize } 115 | } 116 | ``` 117 | 118 | ### TypeMapper.fs 119 | 120 | Maps C types to F# equivalents: 121 | 122 | ```fsharp 123 | let mapCType (cType: CType) : FSharpType = 124 | match cType with 125 | | CPrimitive "uint32_t" -> FSharpType.UInt32 126 | | CPrimitive "int32_t" -> FSharpType.Int32 127 | | CPointer (inner, qualifiers) -> 128 | let region = if hasVolatile qualifiers then "peripheral" else "sram" 129 | let access = extractAccess qualifiers // from __I, __O, __IO 130 | FSharpType.NativePtr (mapCType inner, region, access) 131 | | CStruct fields -> 132 | FSharpType.Record (fields |> List.map mapField) 133 | | _ -> ... 134 | ``` 135 | 136 | ### CodeGenerator.fs 137 | 138 | Generates F# source code: 139 | 140 | ```fsharp 141 | let generateStruct (name: string) (fields: FieldDef list) = 142 | sprintf """ 143 | [] 144 | type %s = { 145 | %s 146 | } 147 | """ name (fields |> List.map generateField |> String.concat "\n") 148 | 149 | let generateBinding (func: FunctionDef) = 150 | sprintf """ 151 | let %s %s : %s = Unchecked.defaultof<%s> 152 | """ func.Name (generateParams func.Params) func.ReturnType func.ReturnType 153 | ``` 154 | 155 | ### QuotationGenerator.fs (NEW - December 2024) 156 | 157 | Generates F# quotations and active patterns for the nanopass pipeline: 158 | 159 | ```fsharp 160 | // Generate quotation for peripheral descriptor 161 | let generatePeripheralQuotation (peripheral: PeripheralDef) = 162 | sprintf """ 163 | let %sQuotation: Expr = <@ 164 | { Name = "%s" 165 | Instances = Map.ofList [%s] 166 | Layout = { Size = %d; Alignment = %d; Fields = %sFields } 167 | MemoryRegion = Peripheral } 168 | @> 169 | """ peripheral.Name peripheral.Name 170 | (generateInstanceList peripheral.Instances) 171 | peripheral.Size 172 | peripheral.Alignment 173 | peripheral.Name 174 | 175 | // Generate active pattern for PSG recognition 176 | let generateActivePattern (func: FunctionDef) = 177 | sprintf """ 178 | let (|%s|_|) (node: PSGNode) : %s option = 179 | match node with 180 | | CallToExtern "%s" args -> Some (extractArgs args) 181 | | _ -> None 182 | """ func.PatternName func.ExtractedType func.ExternName 183 | 184 | // Generate MemoryModel record 185 | let generateMemoryModel (target: TargetDef) = 186 | sprintf """ 187 | let %sMemoryModel: MemoryModel = { 188 | TargetFamily = "%s" 189 | PeripheralDescriptors = [%s] 190 | RegisterConstraints = [%s] 191 | Regions = %sRegions 192 | Recognize = recognize%sMemoryOperation 193 | CacheTopology = None 194 | CoherencyModel = None 195 | } 196 | """ target.Name target.Family 197 | (target.Peripherals |> List.map (fun p -> p.Name + "Quotation") |> String.concat "; ") 198 | (generateConstraintList target) 199 | target.Name 200 | target.Name 201 | ``` 202 | 203 | ### DescriptorGenerator.fs (Legacy) 204 | 205 | Generates BAREWire hardware descriptors: 206 | 207 | ```fsharp 208 | let generatePeripheralDescriptor (peripheral: PeripheralDef) = 209 | sprintf """ 210 | let %sDescriptor: PeripheralDescriptor = { 211 | Name = "%s" 212 | Instances = Map.ofList [ 213 | %s 214 | ] 215 | Layout = { 216 | Size = %d 217 | Alignment = %d 218 | Fields = [ 219 | %s 220 | ] 221 | } 222 | MemoryRegion = Peripheral 223 | } 224 | """ peripheral.Name peripheral.Name 225 | (generateInstances peripheral.Instances) 226 | peripheral.Size 227 | peripheral.Alignment 228 | (generateFields peripheral.Fields) 229 | ``` 230 | 231 | ## Library Markers 232 | 233 | Farscape uses special library names that Firefly's Alex recognizes: 234 | 235 | | Library Name | Target | Code Generation | 236 | |--------------|--------|-----------------| 237 | | `__cmsis` | ARM CMSIS HAL | Memory-mapped register access | 238 | | `__fidelity` | Alloy platform | Syscalls or platform APIs | 239 | | `libname` | Standard library | Dynamic linking | 240 | 241 | ## Example: CMSIS GPIO 242 | 243 | Input header: 244 | ```c 245 | typedef struct { 246 | __IO uint32_t MODER; 247 | __IO uint32_t OTYPER; 248 | __I uint32_t IDR; 249 | __IO uint32_t ODR; 250 | __O uint32_t BSRR; 251 | } GPIO_TypeDef; 252 | 253 | #define GPIOA_BASE 0x48000000UL 254 | #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) 255 | 256 | void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init); 257 | void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState); 258 | ``` 259 | 260 | Generated Types.fs: 261 | ```fsharp 262 | namespace CMSIS.STM32L5.GPIO 263 | 264 | open Alloy 265 | 266 | [] 267 | type GPIO_TypeDef = { 268 | MODER: NativePtr 269 | OTYPER: NativePtr 270 | IDR: NativePtr 271 | ODR: NativePtr 272 | BSRR: NativePtr 273 | } 274 | 275 | let GPIOA_BASE = 0x48000000un 276 | ``` 277 | 278 | Generated Bindings.fs: 279 | ```fsharp 280 | module Platform.Bindings = 281 | let halGpioInit gpio init : unit = Unchecked.defaultof 282 | let halGpioWritePin gpio pin state : unit = Unchecked.defaultof 283 | ``` 284 | 285 | Generated Descriptors.fs: 286 | ```fsharp 287 | let gpioDescriptor: PeripheralDescriptor = { 288 | Name = "GPIO" 289 | Instances = Map.ofList ["GPIOA", 0x48000000un] 290 | Layout = { 291 | Size = 0x400 292 | Alignment = 4 293 | Fields = [ 294 | { Name = "MODER"; Offset = 0x00; Type = U32; Access = ReadWrite; ... } 295 | { Name = "OTYPER"; Offset = 0x04; Type = U32; Access = ReadWrite; ... } 296 | { Name = "IDR"; Offset = 0x10; Type = U32; Access = ReadOnly; ... } 297 | { Name = "ODR"; Offset = 0x14; Type = U32; Access = ReadWrite; ... } 298 | { Name = "BSRR"; Offset = 0x18; Type = U32; Access = WriteOnly; ... } 299 | ] 300 | } 301 | MemoryRegion = Peripheral 302 | } 303 | ``` 304 | 305 | ## Current Limitations 306 | 307 | 1. **CppParser Hardcoded**: Currently only parses cJSON.h; needs XParsec wiring 308 | 2. **No Macro Extraction**: #define constants not yet extracted 309 | 3. **Missing CMSIS Qualifiers**: __I, __O, __IO not yet recognized 310 | 4. **Awaiting Dependencies**: Full output requires fsnative types and BAREWire descriptors 311 | 312 | ## Related Documents 313 | 314 | - [BAREWire Integration](./02_BAREWire_Integration.md) 315 | - [fsnative Integration](./03_fsnative_Integration.md) 316 | - [Type Mapping Reference](./Type_Mapping_Reference.md) 317 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Farscape 2 | 3 | F# bindings generator for C/C++ libraries, powered by XParsec. 4 | 5 | [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) 6 | [![License: Commercial](https://img.shields.io/badge/License-Commercial-orange.svg)](Commercial.md) 7 | 8 |

9 | 🚧 Under Active Development 🚧
10 | This project is in early development and not intended for production use. 11 |

12 | 13 | ## Overview 14 | 15 | Farscape automatically generates F# bindings from C/C++ header files. Using XParsec for header parsing, it produces type-safe F# code that integrates seamlessly with the Fidelity native compilation toolchain. 16 | 17 | ### Key Characteristics 18 | 19 | - **XParsec-Powered Parsing**: Pure F# parser combinators for C/C++ headers 20 | - **Idiomatic F# Output**: Generates proper F# types, not just raw P/Invoke 21 | - **Fidelity Integration**: Output designed for Firefly native compilation 22 | - **Embedded Focus**: First-class support for CMSIS HAL and peripheral headers 23 | - **BAREWire Descriptors**: Generates peripheral descriptors for hardware targets 24 | 25 | ## The Fidelity Framework 26 | 27 | Farscape is part of the **Fidelity** native F# compilation ecosystem: 28 | 29 | | Project | Role | 30 | |---------|------| 31 | | **[Firefly](https://github.com/speakeztech/firefly)** | AOT compiler: F# → PSG → MLIR → Native binary | 32 | | **[Alloy](https://github.com/speakeztech/alloy)** | Native standard library with platform bindings | 33 | | **[BAREWire](https://github.com/speakeztech/barewire)** | Binary encoding, memory mapping, zero-copy IPC | 34 | | **Farscape** | C/C++ header parsing for native library bindings | 35 | | **[XParsec](https://github.com/speakeztech/xparsec)** | Parser combinators powering PSG traversal and header parsing | 36 | 37 | The name "Fidelity" reflects the framework's core mission: **preserving type and memory safety** from source code through compilation to native execution. 38 | 39 | ## Architecture 40 | 41 | ``` 42 | ┌─────────────────────────────────────────────────────────────────────────┐ 43 | │ Farscape Pipeline │ 44 | ├─────────────────────────────────────────────────────────────────────────┤ 45 | │ │ 46 | │ C/C++ Header ──► XParsec Parser ──► TypeMapper.fs │ 47 | │ (e.g., gpio.h) (HeaderParser) (C → F# types) │ 48 | │ │ │ │ 49 | │ ▼ ▼ │ 50 | │ Declaration AST TypeMapping list │ 51 | │ │ │ │ 52 | │ └──────┬─────────────┘ │ 53 | │ ▼ │ 54 | │ CodeGenerator.fs │ 55 | │ (F# binding generation) │ 56 | │ │ │ 57 | │ ┌──────────┼──────────┐ │ 58 | │ ▼ ▼ ▼ │ 59 | │ F# Bindings BAREWire Fidelity │ 60 | │ (.fs files) Descriptors Externs │ 61 | │ │ 62 | └─────────────────────────────────────────────────────────────────────────┘ 63 | ``` 64 | 65 | ### Core Modules 66 | 67 | | Module | Purpose | 68 | |--------|---------| 69 | | `HeaderParser.fs` | XParsec-based C/C++ header parsing | 70 | | `TypeMapper.fs` | Map C types to F# equivalents | 71 | | `CodeGenerator.fs` | Generate F# binding code | 72 | | `DescriptorGenerator.fs` | Generate BAREWire peripheral descriptors | 73 | | `BindingGenerator.fs` | Orchestrate full pipeline | 74 | 75 | ## XParsec: The Parsing Foundation 76 | 77 | Farscape uses [XParsec](https://github.com/speakeztech/xparsec), the same parser combinator library that powers Firefly's PSG traversal. This provides: 78 | 79 | - **Type-safe parsing**: Errors caught at compile time 80 | - **Composable parsers**: Build complex parsers from simple primitives 81 | - **Excellent error messages**: Precise location and context for parse failures 82 | - **Pure F#**: No external dependencies or code generation 83 | 84 | ```fsharp 85 | // Example: Parsing a C struct field 86 | let fieldParser = 87 | parse { 88 | let! typ = typeSpecifier 89 | let! name = identifier 90 | let! arraySize = optional arrayDeclarator 91 | do! symbol ";" 92 | return { Type = typ; Name = name; ArraySize = arraySize } 93 | } 94 | ``` 95 | 96 | ## Usage 97 | 98 | ```bash 99 | # Basic usage 100 | farscape generate --header path/to/header.h --library libname 101 | 102 | # With include paths (for CMSIS, etc.) 103 | farscape generate --header stm32l5xx_hal_gpio.h \ 104 | --library __cmsis \ 105 | --include-paths ./CMSIS/Core/Include,./STM32L5xx/Include \ 106 | --defines STM32L552xx,USE_HAL_DRIVER \ 107 | --verbose 108 | 109 | # Generate BAREWire peripheral descriptors 110 | farscape generate --header stm32l5xx_hal_gpio.h \ 111 | --output-mode descriptors \ 112 | --library __cmsis 113 | 114 | # Full options 115 | farscape generate [options] 116 | 117 | Options: 118 | -h, --header
Path to C/C++ header file (required) 119 | -l, --library Name of native library (required) 120 | -o, --output Output directory [default: ./output] 121 | -n, --namespace Namespace for generated code 122 | -i, --include-paths Additional include paths (comma-separated) 123 | -d, --defines Preprocessor definitions (comma-separated) 124 | -m, --output-mode Output mode: bindings | descriptors | both 125 | -v, --verbose Verbose output 126 | ``` 127 | 128 | ## Output Modes 129 | 130 | ### F# Bindings (Default) 131 | 132 | Standard F# bindings for use with Firefly: 133 | 134 | ```fsharp 135 | namespace CMSIS.STM32L5.GPIO 136 | 137 | open Alloy 138 | 139 | [] 140 | type GPIO_InitTypeDef = { 141 | Pin: uint32 142 | Mode: uint32 143 | Pull: uint32 144 | Speed: uint32 145 | Alternate: uint32 146 | } 147 | 148 | module HAL = 149 | let GPIO_Init (gpio: nativeptr) (init: nativeptr) : unit = 150 | Platform.Bindings.halGpioInit gpio init 151 | 152 | let GPIO_WritePin (gpio: nativeptr) (pin: uint16) (state: GPIO_PinState) : unit = 153 | Platform.Bindings.halGpioWritePin gpio pin state 154 | ``` 155 | 156 | ### BAREWire Peripheral Descriptors 157 | 158 | For embedded targets, generate descriptors that Alex uses for memory-mapped access: 159 | 160 | ```fsharp 161 | let gpioDescriptor: PeripheralDescriptor = { 162 | Name = "GPIO" 163 | Instances = Map.ofList [ 164 | "GPIOA", 0x48000000un 165 | "GPIOB", 0x48000400un 166 | "GPIOC", 0x48000800un 167 | ] 168 | Layout = { 169 | Fields = [ 170 | { Name = "MODER"; Offset = 0x00; Type = U32; Access = ReadWrite } 171 | { Name = "OTYPER"; Offset = 0x04; Type = U32; Access = ReadWrite } 172 | { Name = "OSPEEDR"; Offset = 0x08; Type = U32; Access = ReadWrite } 173 | { Name = "PUPDR"; Offset = 0x0C; Type = U32; Access = ReadWrite } 174 | { Name = "IDR"; Offset = 0x10; Type = U32; Access = ReadOnly } 175 | { Name = "ODR"; Offset = 0x14; Type = U32; Access = ReadWrite } 176 | { Name = "BSRR"; Offset = 0x18; Type = U32; Access = WriteOnly } 177 | ] 178 | } 179 | MemoryRegion = Peripheral 180 | } 181 | ``` 182 | 183 | ## Type Mapping 184 | 185 | Farscape maps C types to appropriate F# equivalents: 186 | 187 | | C Type | F# Type | Notes | 188 | |--------|---------|-------| 189 | | `int8_t` | `int8` | Signed 8-bit | 190 | | `uint8_t` | `uint8` | Unsigned 8-bit | 191 | | `int16_t` | `int16` | Signed 16-bit | 192 | | `uint16_t` | `uint16` | Unsigned 16-bit | 193 | | `int32_t` / `int` | `int32` | Signed 32-bit | 194 | | `uint32_t` | `uint32` | Unsigned 32-bit | 195 | | `int64_t` | `int64` | Signed 64-bit | 196 | | `uint64_t` | `uint64` | Unsigned 64-bit | 197 | | `float` | `float32` | 32-bit float | 198 | | `double` | `float` | 64-bit float | 199 | | `void*` | `nativeint` | Untyped pointer | 200 | | `T*` | `nativeptr` | Typed pointer | 201 | | `const T*` | `nativeptr` | Read-only typed pointer | 202 | | `T[N]` | `T[]` or inline | Fixed-size array | 203 | 204 | ## Fidelity Integration 205 | 206 | ### Library Markers 207 | 208 | Use special library names that Firefly's Alex component recognizes: 209 | 210 | - `__cmsis` - CMSIS HAL functions → memory-mapped register access 211 | - `__fidelity` - Alloy platform bindings → syscalls or platform APIs 212 | - `libname` - Standard library → dynamic linking 213 | 214 | ### Generated Platform Bindings 215 | 216 | For Fidelity targets, Farscape generates `Platform.Bindings` declarations: 217 | 218 | ```fsharp 219 | module Platform.Bindings = 220 | let halGpioInit gpio init : unit = Unchecked.defaultof 221 | let halGpioWritePin gpio pin state : unit = Unchecked.defaultof 222 | let halGpioReadPin gpio pin : GPIO_PinState = Unchecked.defaultof 223 | ``` 224 | 225 | Alex provides the actual implementations based on target platform. 226 | 227 | ## Examples 228 | 229 | ### CMSIS HAL for STM32 230 | 231 | ```bash 232 | # Generate bindings for GPIO 233 | farscape generate \ 234 | --header STM32L5xx_HAL_Driver/Inc/stm32l5xx_hal_gpio.h \ 235 | --library __cmsis \ 236 | --include-paths CMSIS/Core/Include,STM32L5xx/Include \ 237 | --defines STM32L552xx,USE_HAL_DRIVER \ 238 | --namespace CMSIS.STM32L5.GPIO \ 239 | --output-mode both 240 | ``` 241 | 242 | ### Using Generated Bindings 243 | 244 | ```fsharp 245 | open Alloy 246 | open CMSIS.STM32L5.GPIO 247 | 248 | let blink () = 249 | // Initialize GPIO for LED 250 | let init = GPIO_InitTypeDef( 251 | Pin = GPIO_PIN_5, 252 | Mode = GPIO_MODE_OUTPUT_PP, 253 | Pull = GPIO_NOPULL, 254 | Speed = GPIO_SPEED_FREQ_LOW 255 | ) 256 | HAL.GPIO_Init(GPIOA, &init) 257 | 258 | // Blink loop 259 | while true do 260 | HAL.GPIO_TogglePin(GPIOA, GPIO_PIN_5) 261 | Time.sleep 500 262 | ``` 263 | 264 | Compile with Firefly: 265 | ```bash 266 | firefly compile Blinky.fidproj --target thumbv7em-none-eabihf 267 | ``` 268 | 269 | ## Prerequisites 270 | 271 | - [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) or later 272 | - XParsec (automatically restored via NuGet) 273 | 274 | ## Installation 275 | 276 | ```bash 277 | # Clone the repository 278 | git clone https://github.com/speakeztech/farscape.git 279 | cd farscape 280 | 281 | # Build 282 | dotnet build 283 | 284 | # Install as global tool 285 | dotnet tool install --global --add-source ./nupkg farscape 286 | ``` 287 | 288 | ## Development Status 289 | 290 | Farscape is under active development. Current focus: 291 | 292 | - [x] XParsec-based C header parsing 293 | - [x] Basic type mapping (primitives, pointers, structs) 294 | - [x] F# binding generation 295 | - [ ] C++ header support (classes, templates) 296 | - [ ] Macro/constant extraction 297 | - [ ] BAREWire descriptor generation 298 | - [ ] Function pointer to delegate mapping 299 | 300 | ## Contributing 301 | 302 | Contributions are welcome! Please feel free to submit a Pull Request. 303 | 304 | 1. Fork the repository 305 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 306 | 3. Commit your changes (`git commit -m 'Add amazing feature'`) 307 | 4. Push to the branch (`git push origin feature/amazing-feature`) 308 | 5. Open a Pull Request 309 | 310 | ## License 311 | 312 | Farscape is dual-licensed under both the Apache License 2.0 and a Commercial License. 313 | 314 | ### Open Source License 315 | 316 | For open source projects, academic use, non-commercial applications, and internal tools, use Farscape under the **Apache License 2.0**. 317 | 318 | ### Commercial License 319 | 320 | A Commercial License is required for incorporating Farscape into commercial products or services. See [Commercial.md](Commercial.md) for details. 321 | 322 | ### Patent Notice 323 | 324 | Farscape generates BAREWire peripheral descriptors, which utilize technology covered by U.S. Patent Application No. 63/786,247 "System and Method for Zero-Copy Inter-Process Communication Using BARE Protocol". See BAREWire's [PATENTS.md](https://github.com/speakeztech/barewire/blob/main/PATENTS.md) for licensing details. 325 | 326 | ## Acknowledgments 327 | 328 | - **[XParsec](https://github.com/speakeztech/xparsec)**: Parser combinators for F# 329 | - **[Firefly](https://github.com/speakeztech/firefly)**: F# native compiler 330 | - **[BAREWire](https://github.com/speakeztech/barewire)**: Memory descriptors and IPC 331 | - **ARM CMSIS**: Standard interface for Cortex-M microcontrollers 332 | -------------------------------------------------------------------------------- /docs/02_BAREWire_Integration.md: -------------------------------------------------------------------------------- 1 | # BAREWire Integration 2 | 3 | Farscape generates BAREWire hardware descriptors from parsed C/C++ headers. This document describes how Farscape populates BAREWire types and how the generated descriptors are consumed by Firefly/Alex. 4 | 5 | ## Status 6 | 7 | > **Implementation Status: PLANNED** 8 | > 9 | > BAREWire hardware descriptor types are not yet implemented. This document describes the design specification for Farscape's integration with BAREWire once those types exist. 10 | 11 | ## Dependency Chain 12 | 13 | ``` 14 | CMSIS/HAL Headers 15 | │ 16 | ▼ 17 | Farscape (parse) 18 | │ 19 | ├──▶ Types.fs (F# structs using fsnative types) 20 | │ 21 | ├──▶ Bindings.fs (Platform.Bindings declarations) 22 | │ 23 | └──▶ Descriptors.fs (BAREWire types) ← REQUIRES BAREWire types 24 | ``` 25 | 26 | Farscape cannot generate complete hardware descriptors until BAREWire provides these types: 27 | 28 | | BAREWire Type | Purpose | Status | 29 | |---------------|---------|--------| 30 | | `PeripheralDescriptor` | Complete peripheral definition | PLANNED | 31 | | `PeripheralLayout` | Register set structure | PLANNED | 32 | | `FieldDescriptor` | Individual register definition | PLANNED | 33 | | `AccessKind` | Read/Write constraints | PLANNED | 34 | | `MemoryRegionKind` | Memory classification | PLANNED | 35 | | `BitFieldDescriptor` | Sub-register fields | PLANNED | 36 | | `RegisterType` | Register data types | PLANNED | 37 | 38 | See [BAREWire Hardware Descriptors](~/repos/BAREWire/docs/08%20Hardware%20Descriptors.md) for type definitions. 39 | 40 | ## Descriptor Generation Pipeline 41 | 42 | ### Stage 1: Header Parsing 43 | 44 | Farscape's XParsec-based parser extracts C struct definitions: 45 | 46 | ```c 47 | // Input: CMSIS header 48 | typedef struct { 49 | __IO uint32_t MODER; 50 | __IO uint32_t OTYPER; 51 | __I uint32_t IDR; 52 | __IO uint32_t ODR; 53 | __O uint32_t BSRR; 54 | } GPIO_TypeDef; 55 | ``` 56 | 57 | The parser produces an intermediate representation: 58 | 59 | ```fsharp 60 | type CStructDef = { 61 | Name: string option // Some "GPIO_TypeDef" 62 | Fields: CFieldDef list // [MODER; OTYPER; IDR; ODR; BSRR] 63 | } 64 | 65 | type CFieldDef = { 66 | Qualifiers: CQualifier list // [__IO], [__I], or [__O] 67 | Type: CType // uint32_t 68 | Name: string // "MODER" 69 | ArraySize: int option // None for scalars 70 | } 71 | 72 | type CQualifier = 73 | | Volatile 74 | | Const 75 | | CMSIS_I // __I - volatile const (read-only) 76 | | CMSIS_O // __O - volatile (write-only) 77 | | CMSIS_IO // __IO - volatile (read-write) 78 | ``` 79 | 80 | ### Stage 2: Macro Extraction 81 | 82 | Peripheral instance base addresses come from `#define` macros: 83 | 84 | ```c 85 | // Input 86 | #define GPIOA_BASE 0x48000000UL 87 | #define GPIOB_BASE 0x48000400UL 88 | #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) 89 | #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE) 90 | ``` 91 | 92 | Farscape extracts: 93 | 94 | ```fsharp 95 | type CPeripheralInstance = { 96 | InstanceName: string // "GPIOA" 97 | TypeName: string // "GPIO_TypeDef" 98 | BaseAddress: unativeint // 0x48000000un 99 | } 100 | ``` 101 | 102 | ### Stage 3: Layout Calculation 103 | 104 | Farscape calculates field offsets and struct size: 105 | 106 | ```fsharp 107 | let calculateLayout (fields: CFieldDef list) : PeripheralLayout = 108 | let mutable offset = 0 109 | let mutable maxAlign = 1 110 | 111 | let fieldDescriptors = [ 112 | for field in fields do 113 | let (size, align) = getTypeMetrics field.Type 114 | // Align offset 115 | offset <- alignUp offset align 116 | maxAlign <- max maxAlign align 117 | 118 | yield { 119 | Name = field.Name 120 | Offset = offset 121 | Type = mapCTypeToRegisterType field.Type 122 | Access = mapQualifiersToAccess field.Qualifiers 123 | BitFields = None // Extracted separately 124 | Documentation = None 125 | } 126 | 127 | offset <- offset + size 128 | ] 129 | 130 | { 131 | Size = alignUp offset maxAlign // Total struct size 132 | Alignment = maxAlign 133 | Fields = fieldDescriptors 134 | } 135 | ``` 136 | 137 | ### Stage 4: Descriptor Assembly 138 | 139 | All parsed information is assembled into a `PeripheralDescriptor`: 140 | 141 | ```fsharp 142 | let generatePeripheralDescriptor 143 | (structDef: CStructDef) 144 | (instances: CPeripheralInstance list) 145 | : PeripheralDescriptor = 146 | 147 | { 148 | Name = extractFamilyName structDef.Name // "GPIO" from "GPIO_TypeDef" 149 | Instances = 150 | instances 151 | |> List.map (fun i -> i.InstanceName, i.BaseAddress) 152 | |> Map.ofList 153 | Layout = calculateLayout structDef.Fields 154 | MemoryRegion = Peripheral // CMSIS peripherals are always volatile 155 | } 156 | ``` 157 | 158 | ## CMSIS Qualifier Mapping 159 | 160 | The critical mapping from CMSIS qualifiers to `AccessKind`: 161 | 162 | | CMSIS | C Definition | AccessKind | Meaning | 163 | |-------|--------------|------------|---------| 164 | | `__I` | `volatile const` | `ReadOnly` | Hardware state; writes undefined | 165 | | `__O` | `volatile` | `WriteOnly` | Trigger register; reads undefined | 166 | | `__IO` | `volatile` | `ReadWrite` | Normal volatile register | 167 | 168 | ### Implementation 169 | 170 | ```fsharp 171 | let mapQualifiersToAccess (qualifiers: CQualifier list) : AccessKind = 172 | match qualifiers with 173 | | q when List.contains CMSIS_I q -> ReadOnly 174 | | q when List.contains CMSIS_O q -> WriteOnly 175 | | q when List.contains CMSIS_IO q -> ReadWrite 176 | | q when List.contains Const q && List.contains Volatile q -> ReadOnly 177 | | q when List.contains Volatile q -> ReadWrite 178 | | _ -> ReadWrite // Default for non-volatile (rare in CMSIS) 179 | ``` 180 | 181 | ### Why This Matters 182 | 183 | Access constraints are **hardware-enforced**. The generated `AccessKind` informs: 184 | 185 | 1. **fsnative type generation**: Fields get `readOnly`, `writeOnly`, or `readWrite` measures 186 | 2. **Alex code generation**: Prevents invalid read-modify-write on write-only registers 187 | 3. **Compile-time safety**: Attempts to read a write-only register fail with FS8001 188 | 189 | Example error: 190 | 191 | ```fsharp 192 | // F# code (using Farscape-generated bindings) 193 | let value = gpio.BSRR // Attempt to read write-only register 194 | 195 | // Compile error: 196 | // FS8001: Cannot read from write-only pointer 'BSRR' 197 | ``` 198 | 199 | ## Output Format 200 | 201 | ### Descriptors.fs 202 | 203 | Farscape generates a complete F# module: 204 | 205 | ```fsharp 206 | namespace CMSIS.STM32L5.Descriptors 207 | 208 | open BAREWire.Hardware 209 | 210 | /// GPIO peripheral family descriptor 211 | let gpioDescriptor: PeripheralDescriptor = { 212 | Name = "GPIO" 213 | Instances = Map.ofList [ 214 | "GPIOA", 0x48000000un 215 | "GPIOB", 0x48000400un 216 | "GPIOC", 0x48000800un 217 | "GPIOD", 0x48000C00un 218 | "GPIOE", 0x48001000un 219 | "GPIOF", 0x48001400un 220 | "GPIOG", 0x48001800un 221 | "GPIOH", 0x48001C00un 222 | ] 223 | Layout = { 224 | Size = 0x400 225 | Alignment = 4 226 | Fields = [ 227 | { Name = "MODER"; Offset = 0x00; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Mode register" } 228 | { Name = "OTYPER"; Offset = 0x04; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Output type register" } 229 | { Name = "OSPEEDR"; Offset = 0x08; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Output speed register" } 230 | { Name = "PUPDR"; Offset = 0x0C; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Pull-up/pull-down register" } 231 | { Name = "IDR"; Offset = 0x10; Type = U32; Access = ReadOnly; BitFields = None; Documentation = Some "Input data register" } 232 | { Name = "ODR"; Offset = 0x14; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Output data register" } 233 | { Name = "BSRR"; Offset = 0x18; Type = U32; Access = WriteOnly; BitFields = None; Documentation = Some "Bit set/reset register" } 234 | { Name = "LCKR"; Offset = 0x1C; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Configuration lock register" } 235 | { Name = "AFRL"; Offset = 0x20; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Alternate function low register" } 236 | { Name = "AFRH"; Offset = 0x24; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Alternate function high register" } 237 | { Name = "BRR"; Offset = 0x28; Type = U32; Access = WriteOnly; BitFields = None; Documentation = Some "Bit reset register" } 238 | ] 239 | } 240 | MemoryRegion = Peripheral 241 | } 242 | 243 | /// USART peripheral family descriptor 244 | let usartDescriptor: PeripheralDescriptor = { 245 | Name = "USART" 246 | Instances = Map.ofList [ 247 | "USART1", 0x40013800un 248 | "USART2", 0x40004400un 249 | "USART3", 0x40004800un 250 | "UART4", 0x40004C00un 251 | "UART5", 0x40005000un 252 | "LPUART1", 0x40008000un 253 | ] 254 | Layout = { 255 | Size = 0x400 256 | Alignment = 4 257 | Fields = [ 258 | { Name = "CR1"; Offset = 0x00; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Control register 1" } 259 | { Name = "CR2"; Offset = 0x04; Type = U32; Access = ReadWrite; BitFields = None; Documentation = None } 260 | { Name = "CR3"; Offset = 0x08; Type = U32; Access = ReadWrite; BitFields = None; Documentation = None } 261 | { Name = "BRR"; Offset = 0x0C; Type = U32; Access = ReadWrite; BitFields = None; Documentation = Some "Baud rate register" } 262 | { Name = "GTPR"; Offset = 0x10; Type = U32; Access = ReadWrite; BitFields = None; Documentation = None } 263 | { Name = "RTOR"; Offset = 0x14; Type = U32; Access = ReadWrite; BitFields = None; Documentation = None } 264 | { Name = "RQR"; Offset = 0x18; Type = U32; Access = WriteOnly; BitFields = None; Documentation = Some "Request register" } 265 | { Name = "ISR"; Offset = 0x1C; Type = U32; Access = ReadOnly; BitFields = None; Documentation = Some "Interrupt and status register" } 266 | { Name = "ICR"; Offset = 0x20; Type = U32; Access = WriteOnly; BitFields = None; Documentation = Some "Interrupt flag clear register" } 267 | { Name = "RDR"; Offset = 0x24; Type = U32; Access = ReadOnly; BitFields = None; Documentation = Some "Receive data register" } 268 | { Name = "TDR"; Offset = 0x28; Type = U32; Access = WriteOnly; BitFields = None; Documentation = Some "Transmit data register" } 269 | ] 270 | } 271 | MemoryRegion = Peripheral 272 | } 273 | 274 | /// All descriptors for STM32L5 family 275 | let allDescriptors = [ 276 | gpioDescriptor 277 | usartDescriptor 278 | // ... more peripherals 279 | ] 280 | ``` 281 | 282 | ## Consumption by Firefly/Alex 283 | 284 | ### Memory Catalog 285 | 286 | Alex uses the descriptors to build a memory catalog: 287 | 288 | ```fsharp 289 | // In Alex/Bindings/MemoryCatalog.fs 290 | type MemoryCatalog = { 291 | Peripherals: Map 292 | AddressToPeripheral: Map // address → (family, instance) 293 | } 294 | 295 | let buildCatalog (descriptors: PeripheralDescriptor list) : MemoryCatalog = 296 | let peripherals = descriptors |> List.map (fun d -> d.Name, d) |> Map.ofList 297 | let addressMap = [ 298 | for d in descriptors do 299 | for (instance, addr) in Map.toSeq d.Instances do 300 | yield addr, (d.Name, instance) 301 | ] |> Map.ofList 302 | { Peripherals = peripherals; AddressToPeripheral = addressMap } 303 | ``` 304 | 305 | ### MLIR Generation 306 | 307 | When Alex encounters peripheral access, it uses descriptor info: 308 | 309 | ```fsharp 310 | // Alex sees: gpio.ODR <- 0x20u 311 | 312 | // 1. Look up field in descriptor 313 | let field = gpioDescriptor.Layout.Fields |> List.find (fun f -> f.Name = "ODR") 314 | 315 | // 2. Verify access is legal 316 | match field.Access with 317 | | ReadOnly -> failwith "Cannot write to read-only register" 318 | | WriteOnly | ReadWrite -> () // OK 319 | 320 | // 3. Generate volatile store with correct offset 321 | let baseAddr = Map.find "GPIOA" gpioDescriptor.Instances // 0x48000000un 322 | let ptr = builder.BuildIntToPtr baseAddr 323 | let fieldPtr = builder.BuildGEP ptr [| int64 field.Offset |] 324 | builder.BuildVolatileStore value fieldPtr 325 | ``` 326 | 327 | ### Tree-Shaking 328 | 329 | Only referenced descriptors are included in final binary: 330 | 331 | 1. Reachability analysis identifies used peripherals 332 | 2. Unused peripheral descriptors are eliminated 333 | 3. Final binary contains minimal metadata 334 | 335 | ## Bit Field Extraction (Future) 336 | 337 | CMSIS headers define bit fields via macros: 338 | 339 | ```c 340 | #define USART_CR1_UE_Pos 0U 341 | #define USART_CR1_UE_Msk (0x1UL << USART_CR1_UE_Pos) 342 | #define USART_CR1_UE USART_CR1_UE_Msk 343 | 344 | #define USART_CR1_RE_Pos 2U 345 | #define USART_CR1_RE_Msk (0x1UL << USART_CR1_RE_Pos) 346 | #define USART_CR1_RE USART_CR1_RE_Msk 347 | ``` 348 | 349 | Future Farscape versions will extract these into `BitFieldDescriptor`: 350 | 351 | ```fsharp 352 | { Name = "UE"; Position = 0; Width = 1; Access = ReadWrite } 353 | { Name = "RE"; Position = 2; Width = 1; Access = ReadWrite } 354 | ``` 355 | 356 | ## Implementation Roadmap 357 | 358 | ### Immediate (Required for BAREWire types) 359 | 360 | 1. BAREWire adds types to `src/Core/Hardware/` 361 | 2. Farscape references BAREWire 362 | 3. DescriptorGenerator.fs outputs `PeripheralDescriptor` instances 363 | 364 | ### Near-term 365 | 366 | 1. Macro extraction for base addresses 367 | 2. CMSIS qualifier recognition (`__I`, `__O`, `__IO`) 368 | 3. Struct layout calculation 369 | 370 | ### Future 371 | 372 | 1. Bit field extraction from `_Pos`/`_Msk` macros 373 | 2. Documentation extraction from comments 374 | 3. Peripheral dependency relationships 375 | 376 | ## Related Documents 377 | 378 | | Document | Location | 379 | |----------|----------| 380 | | BAREWire Hardware Descriptors | `~/repos/BAREWire/docs/08 Hardware Descriptors.md` | 381 | | fsnative Integration | `./03_fsnative_Integration.md` | 382 | | Memory Interlock Requirements | `~/repos/Firefly/docs/Memory_Interlock_Requirements.md` | 383 | -------------------------------------------------------------------------------- /docs/03_fsnative_Integration.md: -------------------------------------------------------------------------------- 1 | # fsnative Integration 2 | 3 | Farscape generates F# bindings that use fsnative's phantom type measures for type-safe peripheral access. This document describes how Farscape uses fsnative types and the dependency relationship. 4 | 5 | ## Status 6 | 7 | > **Implementation Status: PLANNED** 8 | > 9 | > fsnative phantom type measures for memory regions and access constraints are in development. Full integration requires UMX absorption into fsnative. 10 | 11 | ## Dependency Chain 12 | 13 | ``` 14 | FSharp.UMX ──absorption──▶ fsnative ──provides types to──▶ Farscape 15 | ``` 16 | 17 | Farscape depends on fsnative providing: 18 | 19 | | fsnative Type | Purpose | Status | 20 | |---------------|---------|--------| 21 | | `NativePtr<'T, 'region, 'access>` | Type-safe pointer with measures | In development | 22 | | `[] type peripheral` | Memory-mapped I/O region | Planned | 23 | | `[] type sram` | Normal RAM region | Planned | 24 | | `[] type flash` | Read-only storage region | Planned | 25 | | `[] type readOnly` | Read-only access | Planned | 26 | | `[] type writeOnly` | Write-only access | Planned | 27 | | `[] type readWrite` | Read-write access | Planned | 28 | 29 | ## The UMX Foundation 30 | 31 | FSharp.UMX provides the `[]` attribute that allows measures on non-numeric types: 32 | 33 | ```fsharp 34 | // FSharp.UMX pattern 35 | [] 36 | type string<[] 'u> = string 37 | 38 | // fsnative will provide 39 | [] 40 | type NativePtr<'T, [] 'region, [] 'access> = nativeptr<'T> 41 | ``` 42 | 43 | Without UMX absorption, Farscape can only generate untyped `nativeint` or `nativeptr<'T>` without region/access safety. 44 | 45 | ## Type Generation 46 | 47 | ### From C to F# Types 48 | 49 | Farscape maps C types with CMSIS qualifiers to fsnative types: 50 | 51 | | C Declaration | CMSIS Meaning | Generated F# Type | 52 | |---------------|---------------|-------------------| 53 | | `__IO uint32_t ODR` | Read-write volatile | `NativePtr` | 54 | | `__I uint32_t IDR` | Read-only volatile | `NativePtr` | 55 | | `__O uint32_t BSRR` | Write-only volatile | `NativePtr` | 56 | | `uint32_t data` | Non-volatile | `uint32` (value type) | 57 | 58 | ### Struct Generation 59 | 60 | For CMSIS struct definitions: 61 | 62 | ```c 63 | typedef struct { 64 | __IO uint32_t MODER; 65 | __IO uint32_t OTYPER; 66 | __I uint32_t IDR; 67 | __IO uint32_t ODR; 68 | __O uint32_t BSRR; 69 | } GPIO_TypeDef; 70 | ``` 71 | 72 | Farscape generates: 73 | 74 | ```fsharp 75 | namespace CMSIS.STM32L5.GPIO 76 | 77 | open Alloy 78 | open fsnative.Measures 79 | 80 | [] 81 | type GPIO_TypeDef = { 82 | MODER: NativePtr 83 | OTYPER: NativePtr 84 | IDR: NativePtr 85 | ODR: NativePtr 86 | BSRR: NativePtr 87 | } 88 | ``` 89 | 90 | ### Base Address Constants 91 | 92 | Peripheral instance addresses: 93 | 94 | ```c 95 | #define GPIOA_BASE 0x48000000UL 96 | #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) 97 | ``` 98 | 99 | Generates: 100 | 101 | ```fsharp 102 | /// GPIOA base address 103 | let GPIOA_BASE: unativeint = 0x48000000un 104 | 105 | /// GPIOA peripheral instance 106 | let GPIOA: NativePtr = 107 | NativePtr.ofAddress GPIOA_BASE 108 | ``` 109 | 110 | ## Compile-Time Safety 111 | 112 | The phantom type measures provide compile-time enforcement of access constraints. 113 | 114 | ### Read-Only Violation 115 | 116 | ```fsharp 117 | // Attempt to write to read-only register 118 | let gpio = GPIOA 119 | gpio.IDR <- 0x20u // Compile error! 120 | 121 | // Error FS8002: Cannot write to read-only pointer 'IDR' 122 | // The field 'IDR' has access constraint 'readOnly' which does not permit writes. 123 | ``` 124 | 125 | ### Write-Only Violation 126 | 127 | ```fsharp 128 | // Attempt to read from write-only register 129 | let gpio = GPIOA 130 | let value = gpio.BSRR // Compile error! 131 | 132 | // Error FS8001: Cannot read from write-only pointer 'BSRR' 133 | // The field 'BSRR' has access constraint 'writeOnly' which does not permit reads. 134 | ``` 135 | 136 | ### Region Mismatch 137 | 138 | ```fsharp 139 | // Attempt to assign peripheral pointer to SRAM pointer 140 | let gpioPtr: NativePtr = GPIOA 141 | let sramPtr: NativePtr = gpioPtr // Compile error! 142 | 143 | // Error FS8003: Memory region mismatch 144 | // Cannot convert from 'peripheral' to 'sram' memory region. 145 | ``` 146 | 147 | ## Generated Module Structure 148 | 149 | Farscape generates a complete module hierarchy: 150 | 151 | ``` 152 | CMSIS/ 153 | └── STM32L5/ 154 | ├── GPIO/ 155 | │ ├── Types.fs ← Type definitions 156 | │ ├── Bindings.fs ← HAL function bindings 157 | │ └── Descriptors.fs ← BAREWire descriptors 158 | ├── USART/ 159 | │ ├── Types.fs 160 | │ ├── Bindings.fs 161 | │ └── Descriptors.fs 162 | └── RCC/ 163 | ├── Types.fs 164 | ├── Bindings.fs 165 | └── Descriptors.fs 166 | ``` 167 | 168 | ### Types.fs (Full Example) 169 | 170 | ```fsharp 171 | namespace CMSIS.STM32L5.GPIO 172 | 173 | open Alloy 174 | open fsnative.Measures 175 | open System.Runtime.InteropServices 176 | 177 | // ============================================================================ 178 | // GPIO Register Types 179 | // ============================================================================ 180 | 181 | /// GPIO port mode register bits 182 | [] 183 | type GPIO_Mode = 184 | | Input = 0b00u 185 | | Output = 0b01u 186 | | AltFunc = 0b10u 187 | | Analog = 0b11u 188 | 189 | /// GPIO output type 190 | [] 191 | type GPIO_OutputType = 192 | | PushPull = 0u 193 | | OpenDrain = 1u 194 | 195 | /// GPIO output speed 196 | [] 197 | type GPIO_Speed = 198 | | Low = 0b00u 199 | | Medium = 0b01u 200 | | High = 0b10u 201 | | VeryHigh = 0b11u 202 | 203 | /// GPIO pull-up/pull-down 204 | [] 205 | type GPIO_Pull = 206 | | None = 0b00u 207 | | PullUp = 0b01u 208 | | PullDown = 0b10u 209 | 210 | /// GPIO pin state 211 | [] 212 | type GPIO_PinState = 213 | | Reset = 0u 214 | | Set = 1u 215 | 216 | // ============================================================================ 217 | // GPIO Peripheral Structure 218 | // ============================================================================ 219 | 220 | /// GPIO peripheral register structure 221 | /// Matches CMSIS GPIO_TypeDef layout exactly 222 | [] 223 | type GPIO_TypeDef = { 224 | /// Port mode register 225 | MODER: NativePtr 226 | 227 | /// Output type register 228 | OTYPER: NativePtr 229 | 230 | /// Output speed register 231 | OSPEEDR: NativePtr 232 | 233 | /// Pull-up/pull-down register 234 | PUPDR: NativePtr 235 | 236 | /// Input data register (read-only) 237 | IDR: NativePtr 238 | 239 | /// Output data register 240 | ODR: NativePtr 241 | 242 | /// Bit set/reset register (write-only) 243 | BSRR: NativePtr 244 | 245 | /// Configuration lock register 246 | LCKR: NativePtr 247 | 248 | /// Alternate function low register (pins 0-7) 249 | AFRL: NativePtr 250 | 251 | /// Alternate function high register (pins 8-15) 252 | AFRH: NativePtr 253 | 254 | /// Bit reset register (write-only) 255 | BRR: NativePtr 256 | } 257 | 258 | /// GPIO initialization structure 259 | [] 260 | type GPIO_InitTypeDef = { 261 | Pin: uint32 262 | Mode: GPIO_Mode 263 | Pull: GPIO_Pull 264 | Speed: GPIO_Speed 265 | Alternate: uint32 266 | } 267 | 268 | // ============================================================================ 269 | // Peripheral Instances 270 | // ============================================================================ 271 | 272 | /// GPIOA base address 273 | let GPIOA_BASE: unativeint = 0x48000000un 274 | 275 | /// GPIOB base address 276 | let GPIOB_BASE: unativeint = 0x48000400un 277 | 278 | /// GPIOC base address 279 | let GPIOC_BASE: unativeint = 0x48000800un 280 | 281 | /// GPIOD base address 282 | let GPIOD_BASE: unativeint = 0x48000C00un 283 | 284 | /// GPIOE base address 285 | let GPIOE_BASE: unativeint = 0x48001000un 286 | 287 | /// GPIOF base address 288 | let GPIOF_BASE: unativeint = 0x48001400un 289 | 290 | /// GPIOG base address 291 | let GPIOG_BASE: unativeint = 0x48001800un 292 | 293 | /// GPIOH base address 294 | let GPIOH_BASE: unativeint = 0x48001C00un 295 | 296 | /// GPIOA peripheral instance 297 | let GPIOA: NativePtr = 298 | NativePtr.ofAddress GPIOA_BASE 299 | 300 | /// GPIOB peripheral instance 301 | let GPIOB: NativePtr = 302 | NativePtr.ofAddress GPIOB_BASE 303 | 304 | /// GPIOC peripheral instance 305 | let GPIOC: NativePtr = 306 | NativePtr.ofAddress GPIOC_BASE 307 | 308 | /// GPIOD peripheral instance 309 | let GPIOD: NativePtr = 310 | NativePtr.ofAddress GPIOD_BASE 311 | 312 | /// GPIOE peripheral instance 313 | let GPIOE: NativePtr = 314 | NativePtr.ofAddress GPIOE_BASE 315 | 316 | /// GPIOF peripheral instance 317 | let GPIOF: NativePtr = 318 | NativePtr.ofAddress GPIOF_BASE 319 | 320 | /// GPIOG peripheral instance 321 | let GPIOG: NativePtr = 322 | NativePtr.ofAddress GPIOG_BASE 323 | 324 | /// GPIOH peripheral instance 325 | let GPIOH: NativePtr = 326 | NativePtr.ofAddress GPIOH_BASE 327 | 328 | // ============================================================================ 329 | // Pin Definitions 330 | // ============================================================================ 331 | 332 | let GPIO_PIN_0: uint32 = 0x0001u 333 | let GPIO_PIN_1: uint32 = 0x0002u 334 | let GPIO_PIN_2: uint32 = 0x0004u 335 | let GPIO_PIN_3: uint32 = 0x0008u 336 | let GPIO_PIN_4: uint32 = 0x0010u 337 | let GPIO_PIN_5: uint32 = 0x0020u 338 | let GPIO_PIN_6: uint32 = 0x0040u 339 | let GPIO_PIN_7: uint32 = 0x0080u 340 | let GPIO_PIN_8: uint32 = 0x0100u 341 | let GPIO_PIN_9: uint32 = 0x0200u 342 | let GPIO_PIN_10: uint32 = 0x0400u 343 | let GPIO_PIN_11: uint32 = 0x0800u 344 | let GPIO_PIN_12: uint32 = 0x1000u 345 | let GPIO_PIN_13: uint32 = 0x2000u 346 | let GPIO_PIN_14: uint32 = 0x4000u 347 | let GPIO_PIN_15: uint32 = 0x8000u 348 | let GPIO_PIN_All: uint32 = 0xFFFFu 349 | ``` 350 | 351 | ### Bindings.fs (Full Example) 352 | 353 | ```fsharp 354 | namespace CMSIS.STM32L5.GPIO 355 | 356 | open Alloy 357 | open fsnative.Measures 358 | 359 | /// HAL GPIO function bindings 360 | /// These are Platform.Bindings declarations that Alex recognizes 361 | module Platform.Bindings = 362 | 363 | /// Initialize GPIO peripheral 364 | let halGpioInit 365 | (gpio: NativePtr) 366 | (init: GPIO_InitTypeDef) 367 | : unit = 368 | Unchecked.defaultof 369 | 370 | /// De-initialize GPIO peripheral 371 | let halGpioDeInit 372 | (gpio: NativePtr) 373 | (pin: uint32) 374 | : unit = 375 | Unchecked.defaultof 376 | 377 | /// Read input pin state 378 | let halGpioReadPin 379 | (gpio: NativePtr) 380 | (pin: uint32) 381 | : GPIO_PinState = 382 | Unchecked.defaultof 383 | 384 | /// Write output pin state 385 | let halGpioWritePin 386 | (gpio: NativePtr) 387 | (pin: uint32) 388 | (state: GPIO_PinState) 389 | : unit = 390 | Unchecked.defaultof 391 | 392 | /// Toggle output pin 393 | let halGpioTogglePin 394 | (gpio: NativePtr) 395 | (pin: uint32) 396 | : unit = 397 | Unchecked.defaultof 398 | 399 | /// Lock GPIO configuration 400 | let halGpioLockPin 401 | (gpio: NativePtr) 402 | (pin: uint32) 403 | : int = // HAL_StatusTypeDef 404 | Unchecked.defaultof 405 | ``` 406 | 407 | ## Fallback Mode 408 | 409 | When fsnative types are not yet available, Farscape generates degraded output: 410 | 411 | ```fsharp 412 | // Fallback: No phantom type measures 413 | [] 414 | type GPIO_TypeDef = { 415 | MODER: nativeptr // No region/access info 416 | OTYPER: nativeptr 417 | IDR: nativeptr // Should be read-only! 418 | ODR: nativeptr 419 | BSRR: nativeptr // Should be write-only! 420 | } 421 | ``` 422 | 423 | This compiles and works but provides NO compile-time safety for: 424 | - Access constraint violations 425 | - Memory region mismatches 426 | - Volatile semantics 427 | 428 | The `--require-fsnative` flag (future) will make fsnative types mandatory and fail if unavailable. 429 | 430 | ## Implementation Roadmap 431 | 432 | ### Phase 1: Basic Type Generation (Current) 433 | 434 | - Map C primitives to F# primitives 435 | - Generate struct layouts with `nativeptr<'T>` 436 | - No phantom types yet 437 | 438 | ### Phase 2: fsnative Integration (After UMX Absorption) 439 | 440 | - Reference fsnative types 441 | - Generate `NativePtr<'T, 'region, 'access>` 442 | - Map CMSIS qualifiers to access measures 443 | 444 | ### Phase 3: Full Safety (After fsnative Maturation) 445 | 446 | - Compile-time access constraint checking 447 | - Region mismatch detection 448 | - Integration with Alex for volatile MLIR generation 449 | 450 | ## Coordination with fsnative Development 451 | 452 | Farscape and fsnative must develop in lockstep: 453 | 454 | | fsnative Milestone | Farscape Response | 455 | |-------------------|-------------------| 456 | | UMX absorption complete | Reference `[]` | 457 | | Memory measures defined | Generate `NativePtr<'T, peripheral, _>` | 458 | | Access measures defined | Generate `NativePtr<'T, _, readOnly>` etc. | 459 | | Error codes defined | Document FS8001-FS8003 constraints | 460 | | NativePtr.fs complete | Full type-safe output | 461 | 462 | Communication channel: The `~/repos/Firefly/docs/Memory_Interlock_Requirements.md` document tracks this dependency chain. 463 | 464 | ## Related Documents 465 | 466 | | Document | Location | 467 | |----------|----------| 468 | | Memory Interlock Requirements | `~/repos/Firefly/docs/Memory_Interlock_Requirements.md` | 469 | | BAREWire Integration | `./02_BAREWire_Integration.md` | 470 | | fsnative Specification | `~/repos/fsnative-spec/docs/fidelity/FNCS_Specification.md` | 471 | | UMX Integration Plan | `~/repos/FSharp.UMX/docs/fidelity/UMX_Integration_Plan.md` | 472 | --------------------------------------------------------------------------------