Skip to content

Commit ae29526

Browse files
committed
doc: Added more info on bindings and interop
1 parent bb58183 commit ae29526

16 files changed

Lines changed: 1414 additions & 267 deletions

.config/dotnet-tools.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"isRoot": true,
44
"tools": {
55
"fable": {
6-
"version": "5.0.0-alpha.17",
6+
"version": "5.0.0-alpha.20",
77
"commands": [
88
"fable"
99
],

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"fastapi",
77
"Feliz",
88
"Hashnode",
9+
"pyname",
910
"stroustrup"
1011
]
1112
}

BLOGPOST-PLAN.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Fable.Python Literate Converter
2+
3+
## Project Overview
4+
5+
A self-documenting literate programming converter written in F# that transpiles to Python via Fable.Python. The converter processes `.fsx` files with embedded Markdown comments and outputs GitHub-flavored Markdown suitable for publishing on platforms like Hashnode.
6+
7+
**The meta twist:** The converter's source code IS the blog post - it parses itself to generate the Markdown that documents how it works.
8+
9+
## Goals
10+
11+
1. Create a practical tool for converting F# literate scripts to publishable Markdown
12+
2. Demonstrate Fable.Python capabilities through a real-world example
13+
3. Produce a blog post about Fable.Python that dogfoods the toolchain
14+
4. Keep it minimal - ship v1, iterate later
15+
16+
## Input Format
17+
18+
F# script files (`.fsx`) using FSharp.Formatting literate conventions:
19+
20+
```fsharp
21+
(**
22+
# This is Markdown
23+
24+
Regular markdown content goes here between comment delimiters.
25+
*)
26+
27+
let code = "This becomes a fenced code block"
28+
29+
(*** hide ***)
30+
let hiddenSetup = "This code is excluded from output"
31+
32+
(** More markdown explaining the next section *)
33+
34+
let moreCode () =
35+
printfn "visible in output"
36+
```
37+
38+
## Output Format
39+
40+
GitHub-flavored Markdown with fenced code blocks:
41+
42+
````markdown
43+
# This is Markdown
44+
45+
Regular markdown content goes here between comment delimiters.
46+
47+
```fsharp
48+
let code = "This becomes a fenced code block"
49+
```
50+
51+
More markdown explaining the next section
52+
53+
```fsharp
54+
let moreCode () =
55+
printfn "visible in output"
56+
```
57+
````
58+
59+
## Core Parsing Logic
60+
61+
### States
62+
63+
The parser operates as a simple state machine:
64+
65+
- **Markdown mode**: Inside `(** ... *)` blocks → emit content as-is
66+
- **Code mode**: F# code outside comment blocks → accumulate and wrap in fenced blocks
67+
- **Hidden mode**: After `(*** hide ***)` → skip until next markdown block
68+
69+
### Transformations
70+
71+
1. `(** ... *)` → Extract inner content, trim, emit as Markdown
72+
2. `(*** hide ***)` → Enter hidden mode, emit nothing
73+
3. `(*** include-python: sym1, sym2 ***)` → Extract symbols from transpiled Python, emit as fenced block
74+
4. Regular F# code → Wrap in ``` fsharp ``` fenced block
75+
5. Consecutive blank lines in code → Preserve reasonable whitespace
76+
6. `#load`, `#r` directives → Optionally hide or include based on config
77+
78+
## Implementation Approach
79+
80+
### File Structure
81+
82+
```txt
83+
fsx2md/
84+
├── fsx2md.fsx # The literate source (F# + embedded docs)
85+
├── fsx2md.py # Generated Python output
86+
├── README.md # Generated from fsx2md.fsx (the blog post!)
87+
└── test/
88+
└── example.fsx # Test input file
89+
```
90+
91+
### F# Implementation Sketch
92+
93+
```fsharp
94+
type ParserState =
95+
| InMarkdown
96+
| InCode
97+
| Hidden
98+
99+
type Line =
100+
| MarkdownStart // (**
101+
| MarkdownEnd // *)
102+
| HideCommand // (*** hide ***)
103+
| IncludePython of string list // (*** include-python: sym1, sym2 ***)
104+
| CodeLine of string
105+
| BlankLine
106+
107+
let classifyLine (line: string) : Line = ...
108+
109+
let processFile (lines: string seq) : string seq = ...
110+
```
111+
112+
### Key Design Decisions
113+
114+
1. **Line-by-line processing** vs parser combinators
115+
- Start simple with line-by-line + state machine
116+
- Parser combinators are overkill for this grammar
117+
118+
2. **Fable.Python compatibility**
119+
- Use `Fable.Core` attributes where needed
120+
- Stick to Fable-compatible F# subset
121+
- File I/O via Python interop (`open`, `read`, `write`)
122+
123+
3. **Minimal v1 scope**
124+
- Handle `(** *)` blocks and `(*** hide ***)`
125+
- Support `(*** include-python: sym1, sym2 ***)` for showing transpiled Python
126+
- Skip: `define`, `module=`, `lang=`, evaluation
127+
- These can be added in v2 if needed
128+
129+
## Build & Run Pipeline
130+
131+
```bash
132+
# 1. Transpile F# to Python
133+
dotnet fable fsx2md.fsx --lang python -o .
134+
135+
# 2. Run the converter on itself
136+
python fsx2md.py fsx2md.fsx > README.md
137+
138+
# 3. Preview or publish
139+
# Copy README.md to Hashnode or render locally
140+
```
141+
142+
## Blog Post Structure (Generated Output)
143+
144+
The generated README.md / blog post will naturally follow the code structure:
145+
146+
1. **Introduction** - What we're building and why
147+
2. **The Problem** - FSharp.Formatting outputs 4-space indented code, not fenced blocks
148+
3. **The Solution** - A self-parsing literate converter
149+
4. **Implementation** - Walking through the F# code with explanations
150+
5. **Fable.Python in Action** - Showing the transpiled Python
151+
6. **Running It** - How to use the tool
152+
7. **Conclusion** - The recursive beauty of self-documenting tools
153+
154+
## Success Criteria
155+
156+
- [ ] Converter successfully parses its own source file
157+
- [ ] Output Markdown renders correctly on Hashnode with syntax highlighting
158+
- [ ] Python output is clean and readable (good Fable.Python showcase)
159+
- [ ] Blog post is coherent and tells a good story
160+
- [ ] Total implementation < 200 lines of F#
161+
162+
## Future Enhancements (v2+)
163+
164+
- Support `(*** define: name ***)` for reorderable code
165+
- Support `lang=` for non-F# code blocks
166+
- Evaluation and output embedding
167+
- Watch mode for live preview
168+
- Configuration file for customization

chapters/compatibility.fs

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,68 @@ Understanding what works and what doesn't is crucial when targeting Python
77
with Fable. This chapter covers supported features, limitations, and
88
important differences from .NET.
99
10+
## Common Types and Objects
11+
12+
Some F#/.NET types have counterparts in Python. Fable takes advantage of this
13+
to compile to native types that are more performant and reduce code size.
14+
The most important common types are:
15+
16+
| F#/.NET Type | Python Type | Notes |
17+
| ------------------------ | ------------- | ------------------------------- |
18+
| `string` | `str` | Behaves the same |
19+
| `bool` | `bool` | Behaves the same |
20+
| `char` | `str` | Compiled as string of length 1 |
21+
| `Tuple` | `tuple` | Native Python tuple |
22+
| `ResizeArray<T>` | `list` | Native Python list |
23+
| `Dictionary<K,V>` | `dict` | Native Python dict |
24+
| `seq<T>` / `IEnumerable` | iterator | Uses `__iter__` protocol |
25+
| `Array` | `FSharpArray` | Custom wrapper for F# semantics |
26+
27+
## .NET Base Class Library
28+
29+
Fable provides support for some .NET BCL classes. The following are translated
30+
to Python with most methods available:
31+
32+
| .NET Type | Python Type |
33+
| -------------------------------------------- | ----------- |
34+
| `System.String` | `str` |
35+
| `System.Boolean` | `bool` |
36+
| `System.Char` | `str` |
37+
| `System.DateTime` | `datetime` |
38+
| `System.Decimal` | `decimal` |
39+
| `System.Collections.Generic.List<T>` | `list` |
40+
| `System.Collections.Generic.Dictionary<K,V>` | `dict` |
41+
42+
## FSharp.Core
43+
44+
Most FSharp.Core operators are supported, including formatting with `sprintf`,
45+
`printfn`, and `failwithf`. The following types from FSharp.Core translate to Python:
46+
47+
| F# Type | Python |
48+
| ----------------- | -------------------------- |
49+
| `Tuple` | `tuple` |
50+
| `Option<T>` | erased (see caveats) |
51+
| `string` | `str` |
52+
| `List<T>` | `List.fs` (immutable list) |
53+
| `Map<K,V>` | `Map.fs` (immutable map) |
54+
| `Set<T>` | `Set.fs` (immutable set) |
55+
| `ResizeArray<T>` | `list` |
56+
| Record types | `@dataclass` |
57+
| Anonymous Records | `dict` |
58+
59+
## Interfaces and Protocols
60+
61+
.NET interfaces map to Python protocols and special methods:
62+
63+
| .NET Interface | Python | Purpose |
64+
| -------------- | ------------------------ | ----------------------------------- |
65+
| `IEquatable` | `__eq__` | Equality comparison |
66+
| `IEnumerator` | `__next__` | Iterator protocol |
67+
| `IEnumerable` | `__iter__` | For-loop iteration |
68+
| `IComparable` | `__lt__` + `__eq__` | Ordering and sorting |
69+
| `IDisposable` | `__enter__` + `__exit__` | Context managers (`with` statement) |
70+
| `ToString()` | `__str__` | String representation |
71+
1072
## Fully Supported Features
1173
1274
### Core Types
@@ -29,6 +91,8 @@ let numbers = [ 1; 2; 3; 4; 5 ]
2991
// ResizeArray -> Python list (native)
3092
let mutableList = ResizeArray<int>()
3193

94+
(*** include-python: greeting, is_enabled, coordinates, numbers, mutable_list ***)
95+
3296
(**
3397
### Functions and Lambdas
3498
@@ -80,6 +144,8 @@ let person = {
80144
Email = Some "alice@example.com"
81145
}
82146

147+
(*** include-python: Person ***)
148+
83149
(**
84150
### Discriminated Unions
85151
@@ -142,8 +208,9 @@ let someValue = Some 42 // Compiles to just: 42
142208
let noneValue = None // Compiles to: None
143209

144210
(**
145-
This works fine for most cases, but be careful with nested options -
146-
`Some None` vs `None` can be ambiguous.
211+
Note that Fable.Python uses a `SomeWrapper` class to handle nested options correctly.
212+
`Some None` compiles to `SomeWrapper(None)`, which is distinct from plain `None`.
213+
This means `Some (Some x)`, `Some None`, and `None` are all properly distinguishable.
147214
148215
### Multi-line Lambdas
149216
@@ -164,13 +231,38 @@ let processed =
164231
(**
165232
### Numeric Types
166233
167-
Most numerics use custom wrappers to maintain F# semantics. `bigint` uses
168-
Python's native `int`:
234+
Numeric types in Fable.Python are implemented using custom PyO3 wrapper types
235+
written in Rust. These wrappers maintain F#-style semantics (like proper overflow
236+
behavior) while integrating seamlessly with Python.
237+
238+
| F# Type | .NET Type | Python Type | Notes |
239+
| -------------------- | ---------- | ----------- | -------------------------------------- |
240+
| `int` | Int32 | Int32 | Custom wrapper with overflow semantics |
241+
| `int64` | Int64 | Int64 | Custom wrapper |
242+
| `int16` | Int16 | Int16 | Custom wrapper |
243+
| `byte` | Byte | UInt8 | Custom wrapper |
244+
| `sbyte` | SByte | Int8 | Custom wrapper |
245+
| `uint16` | UInt16 | UInt16 | Custom wrapper |
246+
| `uint32` | UInt32 | UInt32 | Custom wrapper |
247+
| `uint64` | UInt64 | UInt64 | Custom wrapper |
248+
| `float` / `double` | Double | Float64 | Custom wrapper |
249+
| `float32` / `single` | Single | Float32 | Custom wrapper |
250+
| `bigint` | BigInteger | int | Native Python type |
251+
| `nativeint` | IntPtr | int | Native Python type |
252+
253+
The wrapper types ensure type safety and correct arithmetic behavior:
169254
*)
170255

171256
let small: int = 42
172257
let big: bigint = 12345678901234567890I
173258

259+
// Wrapper types maintain proper overflow semantics
260+
let maxInt: int = System.Int32.MaxValue
261+
let wrapped: int = maxInt + 1 // Wraps around like .NET
262+
263+
// bigint uses Python's native arbitrary-precision int
264+
let huge: bigint = 999999999999999999999999999999I
265+
174266
(**
175267
### Computation Expressions
176268

0 commit comments

Comments
 (0)