Name — modified-source-preserving identifiers
The decoder threads a Name value type through anywhere an identifier is
exposed. Name carries either:
Source of original— the identifier as it appeared in the source.-
Modified of original * modified— both the original and a transformed form.
This dual representation lets generators rename or normalize identifiers
without losing the original spelling — which is often required for output
attributes ([<CompiledName "...">]), error messages, or round-tripping.
Creating names
let n1 = Name.create "fooBar", Name.create "type" // backtick-normalized if needed
let n2 = Name.Create "fooBar", Name.Create "type" // raw, no normalization
let n3 = Name.Create("type", "``type``")
let n4 = Name.createModified "type" "``type``"
|
Name.create runs the active normalization (default: F# backtick quoting via
NormalizeIdentifierBackticks). Use the static Create if you need the raw
form.
Reading the value back
Two accessors:
name.ValueOrSource— always the original string.name.ValueOrModified— the modified form if any, otherwise the original.
let original = n3.ValueOrSource // "type"
let display = n3.ValueOrModified // "``type``"
|
Mapping over names
Name exposes three map functions that differ in which string they
transform and how the result is wrapped:
-
Name.map f— appliesfto the modified value (or original if unmodified). If the result equals the original, the name collapses back toSource. -
Name.mapSource f— always appliesfto the original. ReturnsModifiedif the result differs. -
Name.mapModified f— appliesfonly to the modified part of an already- modified name; otherwise no-op.
Pre-baked normalisations
Several common transforms are provided:
Function |
Behaviour |
|---|---|
|
Apply the active normalization (default backticks). |
|
PascalCase the modified / source value. |
|
camelCase the modified / source value. |
|
Uppercase first character only. |
|
Alias for |
|
Alias for |
|
Alias for the appropriate casing. |
|
PascalCase + leading single quote. |
|
PascalCase + leading |
Each for… helper has a source… variant that operates on the original
string regardless of any prior modification.
Customising normalization
The active normalization is global mutable state and can be swapped at
process startup via Name.Normalization.setNormalizeSetting:
open Xantham.Decoder.Name.Normalization
setNormalizeSetting (SafeCustom (fun s -> s.Replace("$", "_")))
Name.create "$foo"
|
Three modes:
Backticks(default) — F#NormalizeIdentifierBackticksonly.SafeCustom f—fruns before backticks; cannot break F# parser.-
Custom f—freplaces backticks entirely. Risky; only use if you know what your output language requires.
Casing as a unit of measure
When you want the type of a Name to encode its casing, the decoder offers
units of measure under Case:
Case.pascalCase.camelCase.modulenameCase.typar
These are tags only — they perform no transformation by themselves. Combine them with the strongly-typed sub-modules to get both the cast and the transformation:
let typeName : Name<Case.pascal> = Name.Pascal.create "foo_bar"
let propName : Name<Case.camel> = Name.Camel.create "FooBar"
let modName : Name<Case.modulename> = Name.Module.create "foo" // → IFoo
let typar : Name<Case.typar> = Name.Typar.create "T" // → 'T
|
The four sub-modules (Name.Pascal, Name.Camel, Name.Module,
Name.Typar) each provide:
fromName— convert from an untypedName, applying the casing.create— convert from astring, applying the casing.fromCase— re-cast from another casing, re-applying the new transform.
For runtime dispatch over an unknown casing, use the CasedName DU:
let any : CasedName = CasedName.Pascal typeName
let underlying : Name = any.Value
Working with cased names generically
Name.Case (the inner module) has measure-aware versions of the common
accessors so you don't have to drop measures manually:
let v1 = Name.Case.valueOrModified typeName
let v2 = Name.Case.map (fun s -> s + "Suffix") propName
let isMod = Name.Case.isModified modName
Forcing measures
Case is a module that provides the unsafe primitives for working with cased
names and powers the strongly typed sub-modules Name.Pascal, Name.Camel,
Name.Module, and Name.Typar.
It DOES NOT perform any transformation on the underlying Name, and so
should be avoided in general use.
One scenario this is useful for is when you have a strongly typed field
such as a field representing a member name which is typed as
Name<Case.camel>, but you need to render a special meaning name such
as Invoke or Item.
// Trying to create a camel cased member named "Invoke"
Name.Camel.create "Invoke" // Name<Case.camel>
|> printfn "❌ %A"
Name.Pascal.create "Invoke" // Name<Case.pascal>
|> Name.Camel.fromCase // Name<Case.camel>
|> printfn "❌ %A"
Name.create "Invoke" // Name
|> Case.addCamelMeasure // Name<Case.camel>
|> printfn "✔️ %A"
|
module Name from Xantham.Decoder
<summary></summary>
<category index="3">Names and Casing</category>
--------------------
type Name = | Modified of original: string * modified: string | Source of original: string static member Create: value: string -> Name + 1 overload static member CreateModified: original: string * modified: string -> Name member ValueOrModified: string member ValueOrSource: string
<summary> Utility type for working with names or manipulating the names of types and members while preserving the original source. </summary>
<category index="3">Names and Casing</category>
--------------------
type Name<'u> = Name
<summary>Provide static typing over the casing of a name</summary>
<category index="3">Names and Casing</category>
<summary> Creates a Name from a string. Will automatically normalize the name with backticks if required.. Use the static member Create if you don't want automatic normalization. </summary>
static member Name.Create: original: string * modified: string -> Name
<summary> Creates a modified Name DU. </summary>
<summary>The original source string regardless of whether the name has been modified.</summary>
<summary>The modified string if the name has been modified; otherwise the original source string.</summary>
System.String.Replace(oldChar: char, newChar: char) : string
System.String.Replace(oldValue: string, newValue: string, comparisonType: System.StringComparison) : string
System.String.Replace(oldValue: string, newValue: string, ignoreCase: bool, culture: System.Globalization.CultureInfo) : string
<summary> This provides unsafe means of removing/adding measures to a <c>Name</c>. Use with caution, as they are not associated with any transformations of the underlying strings. </summary>
<category index="3">Names and Casing</category>
<summary>Measure to signify Pascal casing.</summary>
<category index="3">Names and Casing</category>
<summary> Provides means for working with Pascal case measure annotated names directly. </summary>
<summary>Normalize a string into a <c>Name<pascal></c>.</summary>
<summary>Measure to signify camel casing.</summary>
<category index="3">Names and Casing</category>
<summary> Provides means for working with Camel case measure annotated names directly. </summary>
<summary>Normalize a string into a <c>Name<camel></c>.</summary>
<summary> Measure to signify the name is modified to represent the module interface. This is used to distinguish between the module interface and the module itself. </summary>
<category index="3">Names and Casing</category>
<summary> Provides means for working with Module measure annotated names directly. </summary>
<summary>Normalize a string into a <c>Name<modulename></c>.</summary>
<summary>Measure to signify a type parameter (prefixed with a single quote).</summary>
<category index="3">Names and Casing</category>
<summary> Provides means for working with Typar measure annotated names directly. </summary>
<summary>Normalize a string into a <c>Name<typar></c>.</summary>
module List from Microsoft.FSharp.Collections
--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
<summary> A <c>Name</c> tagged with one of the supported casing measures (Pascal, camel, module, or typar). Useful as an envelope when the casing flavour is decided dynamically rather than known statically. </summary>
<category index="3">Names and Casing</category>
<summary> A name carrying the Pascal-case measure (e.g. type, module, or class names). </summary>
<summary>The underlying untyped <c>Name</c>, dropping the casing measure.</summary>
<summary> Provides some equivalency functions for working with Names that have measures. </summary>
<summary>Re-cast any cased name to a <c>Name<camel></c>, applying camelCase normalization.</summary>
<summary>Tag a <c>Name</c> with the <c>camel</c> casing measure.</summary>