Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions assets/highlighting-tests/fsharp.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// --- F# highlighting test ---
// Exercises: comments, block comments, strings, chars, numbers, directives,
// keywords/control flow, types/members, and backtick identifiers.

#r "System.Runtime"
#r "nuget: Newtonsoft.Json, 13.0.3"
#load "some-script.fsx"

(*** Block comment (not nested in this highlighter) ***)
(* Another block comment *)

open System
open Newtonsoft.Json

module ``My Module With Spaces`` =
let pi = 3.14159
let hex = 0xDEAD_BEEF
let bin = 0b1010_1100
let sci = 1.23e-4
let mutable counter = 0

let name = "Edit"
let path = @"C:\Program Files\Edit\edit.exe"
let escaped = "tab:\t newline:\n quote:\" backslash:\\"
let interpolated = $"Hello, {name}!"
let interpolatedVerbatim = $@"Path: {path}"

let triple = """
This is a triple-quoted string.
It can span multiple lines without escapes.
"""

let chA = 'a'
let chN = '\n'

let rec fib n =
if n <= 1 then n
else fib (n - 1) + fib (n - 2)

let classify x =
match x with
| 0 -> "zero"
| 1 | 2 -> "small"
| _ when x < 0 -> "negative"
| _ -> "other"

let tryParseInt (s: string) =
try
let v = Int32.Parse(s)
Ok v
with
| :? FormatException -> Error "format"
| ex -> Error ex.Message

type Person(name: string, age: int) =
member _.Name = name
member _.Age = age
override _.ToString() = $"{name} ({age})"

let demoLoops () =
for i = 1 to 3 do
counter <- counter + i

while counter < 20 do
counter <- counter + 1

// Method-call style tokenization
let p = Person("Ada", 37)
printfn "%s" (p.ToString())

// Single-backtick identifier fallback (not typical F#, but supported by lexer)
let `singleBacktickIdentifier` = 42

demoLoops ()
115 changes: 115 additions & 0 deletions crates/lsh/definitions/fsharp.lsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#[display_name = "F#"]
#[path = "**/*.fs"]
#[path = "**/*.fsi"]
#[path = "**/*.fsx"]
pub fn fsharp() {
until /$/ {
yield other;

// Line comment
if /\/\/.*/ {
yield comment;

// Preprocessor / script directives
} else if /#(?:if|else|endif|load|r|I|nowarn|light|time|help|quit)\>.*/ {
yield keyword.other;

// Block comment: (* ... *)
} else if /\(\*/ {
loop {
yield comment;
if /\*\)/ {
yield comment;
break;
}
await input;
}

// Triple-quoted string: """ ... """
} else if /"""/ {
loop {
yield string;
if /"""/ {
yield string;
break;
}
await input;
}

// Verbatim (optionally interpolated) string: @"..." or $@"..."
} else if /\$?@"/ {
loop {
yield string;
if /""/ {
// Escaped quote
} else if /"/ {
yield string;
break;
}
await input;
}

// Interpolated string: $"..."
} else if /\$"/ {
until /$/ {
yield string;
if /\\./ {
// Escape sequences
} else if /"/ {
yield string;
break;
}
await input;
}

// Regular string: "..."
} else if /"/ {
until /$/ {
yield string;
if /\\./ {
// Escape sequences
} else if /"/ {
yield string;
break;
}
await input;
}

// Character literal: 'a', '\n', etc.
} else if /'(?:\\.|[^'\\])'/ {
yield string;

// Backtick identifiers: ``identifier with spaces``
} else if /``[^`]+``/ {
yield variable;
} else if /`[^`]+`/ {
yield variable;

// Keywords
} else if /(?:if|then|else|elif|match|with|function|try|finally|for|to|downto|while|do|return|yield|begin|end|in)\>/ {
yield keyword.control;
} else if /(?:let|use|and|rec|mutable|open|module|namespace|type|member|interface|abstract|override|default|static|inline|extern|public|private|internal|new|inherit|as|of|when|exception|raise|lazy|assert|global)\>/ {
yield keyword.other;

// Language constants
} else if /(?:true|false|null)\>/ {
yield constant.language;

// Numbers (best-effort)
} else if /(?i:-?(?:0x[\da-f_]+|0b[01_]+|0o[0-7_]+|[\d_]+\.?[\d_]*|\.[\d_]+)(?:e[+-]?[\d_]+)?)(?:u(?:y|s|l|n)|(?:y|s|l|n)|[fFmM])?/ {
if /\w+/ {
// Not a number after all.
} else {
yield constant.numeric;
}

// Method calls in .NET-style syntax: Foo.Bar(...)
} else if /(\w+)\s*\(/ {
yield $1 as method;
} else if /\w+/ {
// Gobble word chars to align the next iteration on a word boundary.
}

yield other;
}
}